Mybatis部分
1:@Param源码
打上断点之后进入debug,
会进入如下这个方法,然后回调用
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession); } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } }
之后进入这个invoke方法之后会继续进入
然后就可以发现这是一个switch判断
根据comman参数的type类型判断当前执行的是那种类型的sql语句.
ParamNameResolver类的构造器将会返回names,那么来研究一下这个方法都做了些什么
可以发现names里面就是一个键值对,其中键为0,1…,值为你传递的参数
注意这里的 String genericParamName = “param” + (i + 1);
就是由于这句话因此Mybatis中当传递多个参数的时候为什么是使用param1,param2来区分不同的参数
2:#{}与${}的区别
#{}使用的是占位符方式,而${}使用的是字符串拼接技术.
因此#{}再注入数据之后会自动加上 单引号,并且由于我们传递过来的参数大部分为string类型,使得#{}的使用更多一点,并且其还能防止数据注入的问题.
${}使用的是字符串拼接技术,我们再JDBC的时候学习过如果你使用字符串拼接作为sql语句,那么你是需要自己去添加单引号的, ${}就是这样,因此再使用它的时候他不会自动添加单引号,你需要手动添加.
但是有些时候我们的查询等操作是不需要添加单引号的,
1:以表名为查询对象
例如我们根据表名查询,那么我们的表名是不能有单引号的,因此如果你使用#{}就会报错,这个时候就必须使用 ${}
2:查询时参数放入到in中
同时,由于#{}自动添加单引号的特性,使得如果我们要使用 select * from table where id in (#{id}),就会导致由于#{id}自动会添加单引号的问题变成in(‘1,2,3’),而正确写法是in(1,2,3),因此会导致查询不到数据但是不会报错,毕竟加上单引号后就变成了字符串,相当于匹配字符串,没什么问题.
因此我们需要使用 in ( ${id} ) ,这样就会自动拼接为 in (1,2,3)因此能查询到数据
3:需要模糊查询数据的时候
众所周知模糊查询的条件放在单引号中,但是如果使用#{}由于其被放在了单引号中,因此#{}本来以占位符?来占位,就会被单引号修饰变成一个字符串,因此这个时候他就认为没有需要填充数据的地方,就会报错了
Try setting a different JdbcType for this parameter or a different configuration property.
这个时候使用${}就没有问题
3:查询一对多或者多对一的数据
数据库中经常会有一个表中的属性与另外几个表有关系.
例如emp和dept表,一个emp只会有一个dept,但是多个emp可以同时对应一个emp(多对一).
同时也有一个dept对应多个的emp,也就是一个部门可以有多个emp.
即一对多.
用Java的对象表现出来就是这样.
Dept类
public class Dept { private Integer did; private String deptName; private List<Emp> emps }
Emp类
public class Emp { private Integer eid; private String empName; private Integer age; private String sex; private String email; private Dept dept; }
可以发现不同的地方在于Emp中的属性Dept是对象.
但是多个Emp可以同时对应同一个Dept,因此当有多个Emp对象的时候,这是一个多对一的关系.
而Dept中的emps属性是List集合,其中包含了多个的Emp对象,也就是说明了一个Dept中可以包含多个Emp,也就是一对多的关系.
但是我们的表一般不可能直接包含另一个表,而是通过包含另一个表中的某个主键来将自己的这个属性映射到另一个表中去.因此如果我们要查询某个员工,我们必须同时查询到另一个Dept表中的数据才能做到成功的把员工表中的所有消息都输出,这其中包括了Dept的消息.
因此我们肯定需要多表查询的操作,多表查询我们一般用left join等方法.
解决多对一
这种多对一对应的就是Emp中包含的Dept对象,那么如何在查询Emp对象的同时给其中的dept属性赋值呢?
Mybaits提供了三种方法
1:级联属性赋值
这种方式其实就是直接两个表联合起来一起查询,left join,懂得都懂好吧,然后把查询到的dept的数据通过result标签中的property属性直接赋值给dept.did与dept.deptName,这样只要在表中查询到了符合sql表达式的数据就会把这条数据给他插入到Emp对象中去
<!-- 处理多对一映射关系一:级联属性赋值 --> <resultMap id="getEmpAndDept1" type="emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <result property="dept.did" column="did"></result> <result property="dept.deptName" column="dept_name"></result> </resultMap> <select id="getEmpAndDept" resultMap="getEmpAndDept1"> select * from my_emp left join my_dept on my_emp.did = my_dept.did where my_emp.eid = #{eid} </select>
2:通过association标签
和上面那种差不多,不多赘述
<!-- 处理多对一映射关系二:通过association association:处理多对一的映射关系 property:需要处理多对的映射关系的属性名 javaType:该属性的类型 --> <resultMap id="getEmpAndDept2" type="emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <association property="dept" javaType="Dept"> <id property="did" column="did"></id> <result property="deptName" column="dept_name"></result> </association> </resultMap> <select id="getEmpAndDept" resultMap="getEmpAndDept2"> select * from my_emp left join my_dept on my_emp.did = my_dept.did where my_emp.eid = #{eid} </select>
调用结果也一样
3:分布查询
这种方法将查询步骤分为两步,当你需要为dept属性进行填充的时候,他就会去调用DeptMapper的方法把你要求查询的对应的以did为查询条件的数据返回回来,然后你就可以用它返回回来的数据去填充你的dept属性了.
这种方法有一个好处就是你可以开启延迟加载,延迟加载就是说当你需要用到dept这个属性的时候他才会去调用发布查询中的第二个sql语句去查询Dept表中的数据,如果你不需要,那么只会查询Emp表中的数据
<settings> <!--将下划线自动映射为驼峰 em_pname:emPname--> <setting name="mapUnderscoreToCamelCase" value="true"/> <!--开启延迟加载--> <setting name="lazyLoadingEnabled" value="true"/> </settings>
<resultMap id="getEmpAndDeptByStepOne" type="emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <!-- select:设置分布查询的sql的唯一标识(namespace.sqlid或者mapper接口的全类类名.方法名) column:设置分布查询的查询条件 property:需要填充的对象 fetchType:当开启了全局的延迟加载之后,可以通过这个属性手动的控制延迟加载功能 --> <association property="dept" select="com.learn.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo" column="did" fetchType="lazy"> </association> </resultMap> <select id="getEmpAndDeptByStepOne" resultMap="getEmpAndDeptByStepOne"> select * from my_emp where eid = #{eid} </select>
DeptMapper文件
<select id="getEmpAndDeptByStepTwo" resultType="dept"> select * from my_dept where did = #{did} </select>
这里需要介绍一下发布查询中association中的这几个参数
property:就是你需要填充的要去另外一个表中查询的对象
select:因为是分布查询,所以要去调用另一个Mapper文件中的方法来查询其管理的表数据,这里我要查询Dept表的数据,因此我的select属性中填充的是能得到dept对象的接口方法
column:这个属性也就是你传递给要去另一个表中查询数据的查询条件参数,这里我把要查询的emp对象对应的emp表中的did属性作为查询条件去查询对应dept属性,查询到之后就会把这个查询到的数据给他填充到emp对象的dept域中去.
这就是多对一的解决方式.