JAVASSMResultMap关联映射
定西Endless
所以还是重新学一下这个mybatis这块,sql实在是不会写了…
害,当初没好好学,现在真是追悔莫及
映射关系
表结构:
- t_emp_old:
- t_dept:
当字段名和属性名不一致时,该怎么办?
- sql中起别名,比如数据库中是emp_id,而Java属性是empId,那么可以这样写sql:
1
| select emp_id empId,emp_name empName ,age,gender from t_emp where emp_id = #{empId};
|
- 给核心配置文件加上如下设置,就可以自定映射下划线和驼峰
1 2 3 4 5
| <settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
|
所以当时看这个课的时候,是什么心情呢?
自定义映射resultMap
仔细看看笔记,其实也是挺明白的,就是写的地方优点…
- 多对一的映射关系:
- 一对多的映射关系:
resultMap最基本的使用
resultMap中的标签/属性
- id设置主键的映射关系
- result设置其余字段的映射关系
- column字段名
- property属性名
用于处理数据库表字段和Java属性名不匹配的情况:
依然还是数据库中emp_id,emp_name,而java类是empId和empName
这时就可以使用resultMap来自定义映射关系:
1 2 3 4 5 6 7 8 9 10 11 12
| <resultMap id="empResultMap" type="Emp"> <id column="emp_id" property="empId"></id> <result column="emp_name" property="empName"></result> <result column="age" property="age"></result> <result column="gender" property="gender"></result> </resultMap>
<select id="getEmpByEmpId" resultMap="empResultMap"> select * from t_emp_old where emp_id = #{empId}; </select>
|
主要就是emp_id和empId之间的写法,column表示数据库中的字段,而property表示属性名
处理多对一映射关系
场景:查询员工信息以及员工所对应的部门信息
这时,就要注意多对一这种关系如何处理了-如何设置java实体类的属性
- 在一的一方设置多的一方类型的集合
- 在多的一方设置一的一方的类的对象
Emp.java
1 2 3 4 5 6 7
| public class Emp { private Integer empId; private String empName; private Integer age; private String gender; private Dept dept; }
|
Dept.java
1 2 3 4 5
| public class Dept { private Integer deptId; private String deptName; private List<Emp> emps; }
|
定义根据员工id查询员工和该员工对应的部门信息的方法:
1
| Emp getEmpAndDeptByEmpIdNew(@Param("empId") Integer empId);
|
这个就涉及到两个表关联之间的关系了,什么A∪
B和A∩
B,还有各种各样的关系,害,突然意识到,我可能需要把之前mysql的关联查询也重新学一遍了,这几天就少打点游戏吧,把什么left join
,和right join
,和inner join
都重新看一遍…
mapper映射文件中该怎么写(resultMap和sql)
什么意思呢,就是你看这个方法,是根据员工id查询员工信息已经员工对应的部门信息,而员工类中是有一个部门类的,这时通过sql查询,查询出的dept_id
和dept_name
要映射为一个Dept对象,此时就要使用association
来实现了
一共三种方式来处理字段和类对应的关系:
- 级联
- association
- 分布查询
级联处理
什么意思呢,就是单纯使用resultMap
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <resultMap id="empAndDeptMapJiLian" type="Emp"> <id column="emp_id" property="empId"></id> <result column="emp_name" property="empName"></result> <result column="age" property="age"></result> <result column="gender" property="gender"></result> <result column="dept_id" property="dept.deptId"></result> <result column="dept_name" property="dept.deptName"></result> </resultMap>
<select id="getEmpAndDeptByEmpIdNew" resultMap="empAndDeptMapJiLian"> SELECT t_emp_old.*, t_dept.* FROM t_emp_old LEFT JOIN t_dept ON t_emp_old.dept_id = t_dept.dept_id WHERE t_emp_old.emp_id = 1 </select>
|
区别在哪?
区别在于处理dept_id和dept_name和dept属性的映射关系时,使用了result,column依然是查询出的列名,而property对应的就是dept.deptId
和dept.deptName
了,这就相当于,将查询出的字段dept_id与Emp类中dept属性中的deptId属性进行对应(有点拗口,但是这么说没问题啊没问题),此时得到正确的数据:
不用在意dept后面有个emps,那个是toString的输出,只要查到deptId和deptName就算成功了
association处理
这里使用association标签进行多对一的映射关系:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <resultMap id="empAndDeptMap" type="Emp"> <id column="emp_id" property="empId"></id> <result column="emp_name" property="empName"></result> <result column="age" property="age"></result> <result column="gender" property="gender"></result> <association property="dept" javaType="Dept"> <id column="dept_id" property="deptId"></id> <result column="dept_name" property="deptName"></result> </association> </resultMap>
<select id="getEmpAndDeptByEmpIdNew" resultMap="empAndDeptMap"> SELECT t_emp_old.*, t_dept.* FROM t_emp_old LEFT JOIN t_dept ON t_emp_old.dept_id = t_dept.dept_id WHERE t_emp_old.emp_id = 1 </select>
|
这里要注意什么?
要注意association标签的使用,association标签中的属性有property和javaType等,property是属性名-就是Emp类中Dept属性的属性名dept,而javaType就表示该属性是什么类型的:Dept呗,此时就可以在association中正常的写映射关系了
一对一和多对一有什么区别?
没有区别,处理起来是一样的步骤
还有就是,说白了,association
就是用来处理实体类类型的属性的,你看,这个Emp类中有一个Dept类型的属性,就可以通过association
来处理,所以它还可以用来处理一对一的关系
分步查询处理:
23点56分
这个明天再看吧,睡觉
分步查询,也就是使用多个sql进行查询,重点在于,这个查询应该分为几步,每一步应该干什么
所以,查询员工信息以及员工对应的部门信息,可以分为两步:
- 根据员工id查询员工信息
- 根据员工对应的部门id在部门表中查询部门信息
第一步的内容:
接口:
1 2 3 4 5 6
|
Emp getEmpAndDeptByStepOneNew(@Param("empId") Integer empId);
|
sql:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <resultMap id="empAndDeptOne" type="Emp"> <id column="emp_id" property="empId"></id> <result column="emp_name" property="empName"></result> <result column="age" property="age"></result> <result column="gender" property="gender"></result> <association property="dept" select="com.zzmr.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwoNew" column="dept_id"> </association> </resultMap>
<select id="getEmpAndDeptByStepOneNew" resultMap="empAndDeptOne"> select * from t_emp_old where emp_id = #{empId}; </select>
|
这里就要注意association的使用了,比之前多了一个select
属性,它适用于指定下一步查询的sql唯一标识,或者说是方法的全路径,而方法的全路径可以使用idea的copy reference,但是注意,拷过来的是长这样:com.zzmr.mybatis.mapper.EmpMapper#getEmpAndDeptByStepOneNew
,没错,前面都没问题,但是最后那个方法的位置是变成了#
号,所以会导致mybatis找不到对应的sql,要把井号改为点才行,**还有就是column
表示给第二步查询传入的参数
下面看第二步查询:
这里将第二步查询的接口放在了DeptMapper中了,当然,放在EmpMapper是一样的,但是为了结构更加清晰明了,还是放在了DeptMapper中
接口
1 2 3 4 5 6
|
Dept getEmpAndDeptByStepTwoNew(@Param("deptId") Integer deptId);
|
sql:
1 2 3 4 5 6
| <select id="getEmpAndDeptByStepTwoNew" resultType="Dept"> select * from t_dept where dept_id = #{deptId} </select>
|
第二步就简单的多了,可以看出,stepTwo方法接收一个deptId,此时第一步中association中的column
就派上用场了,它就用来指定传给第二步查询的参数,这里要根据查询出员工信息中对应的部门id,在部门表中根据id来查询部门信息
association中的property依然是用于将查询的结果赋值给emp中的dept属性(这里由于第二步查询的返回值是一个dept对象,所以第一步不需要再处理一次dept_id和dept_name跟deptId和deptName的映射关系了)
查询结果:
那能用一条sql查出的结果,为什么要用两条sql呢?
这里就要引入分步查询的优点-延迟加载
延迟加载
需要在配置文件中设置全局配置信息
1 2 3 4
| <settings>
<setting name="lazyLoadingEnabled" value="true"/> </settings>
|
这样就开启了延迟加载,此时修改测试方法,再执行刚才的byStepOne:
1 2 3 4 5 6 7
| @Test public void testGetEmpAndDeptByStepNew() { SqlSession sqlSession = SqlSessionUtil.getSqlSession(); EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); Emp emp = mapper.getEmpAndDeptByStepOneNew(3); System.out.println(emp.getEmpName()); }
|
此时我们只获取emp.getEmpName()而不获取部门信息,此时只会执行一条sql:
把延迟加载配置给注掉,再测试,两条sql:
此时就能感受到延迟加载的好处了
但是延迟加载和另外一个属性也有关:aggressiveLazyLoading
,当开启时,任何该对象的方法调用都会加载该对象的所有属性,否则,每个属性都会按需加载
1
| <setting name="aggressiveLazyLoading" value="false"/>
|
此时就实现了按需加载,获取的数据是什么,就只会执行相应的sql,此时可以通过association和collection中的fetchType属性设置当前的分布查询是否使用延迟加载,fetchType="lazy"
就是开启延迟加载,而等于eager就是立即加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <resultMap id="empAndDeptOne" type="Emp"> <id column="emp_id" property="empId"></id> <result column="emp_name" property="empName"></result> <result column="age" property="age"></result> <result column="gender" property="gender"></result> <association property="dept" fetchType="eager" select="com.zzmr.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwoNew" column="dept_id"> </association> </resultMap>
<select id="getEmpAndDeptByStepOneNew" resultMap="empAndDeptOne"> select * from t_emp_old where emp_id = #{empId}; </select>
|
看,此时fetchType为eager,即使开启了延迟加载和关闭了按需加载,依然是执行全部的sql
总结:
- 想要实现延迟加载,一个
lazyLoadingEnabled=true
即可完成,但是老师建议是加上aggressiveLazyLoading=false
,这样依然是默认延迟加载
- 当配置文件如上面所示,又想要实现立即加载,只需要在associaiton中设置
fetchType=eager
,即可实现立即加载,而不设置或者是设置fetchType=lazy
时,都是延迟加载
ok,多对一搞定了,应该是搞定了,下面继续看一对多
处理一对多映射关系
一共两种方式:
- collection
- 分步查询
collection处理一对多
把上面的部门类再拿下来看一下:
Dept.java
1 2 3 4 5
| public class Dept { private Integer deptId; private String deptName; private List<Emp> emps; }
|
没错,就是在一的一方设置多的一方的集合,其实就是一句话:对一,对应对象,对多,对应集合
重点就在于,将联查得到的员工信息,封装为一个List集合,下图就是sql查询出的结果,可以看出,部门信息肯定是一样的,不同的地方就在于emp的信息,就要把这多个emp信息封装为一个List<Emp>
集合
看sql:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <resultMap id="deptAndEmpMapNew" type="Dept"> <id column="dept_id" property="deptId"></id> <result column="dept_name" property="deptName"></result> <collection property="emps" ofType="Emp"> <id column="emp_id" property="empId"></id> <result column="emp_name" property="empName"></result> <result column="age" property="age"></result> <result column="gender" property="gender"></result> </collection> </resultMap>
<select id="getDeptAndEmpByDeptIdNew" resultMap="deptAndEmpMapNew"> SELECT * FROM t_dept INNER JOIN t_emp_old ON t_emp_old.dept_id = t_dept.dept_id where t_dept.dept_id = #{deptId} </select>
|
这里就用到collection标签了,它可以用于处理一对多和多对多的关系,collection的属性也是有property,表示tpye类中的属性名,什么意思呢,往上看Dept类,是不是有一个List<Emp> emps
,这个emps就是要填在property中的内容,而ofType就表示要封装的集合的泛型,collection会将结果集中的多条emp信息封装为一个emp集合,而每个emp对象中字段的对应关系,就还是和之前的写法一样.
测试:
1 2 3 4 5 6 7
| @Test public void testDeptAndEmpByDeptIdNew() { SqlSession sqlSession = SqlSessionUtil.getSqlSession(); DeptMapper mapper = sqlSession.getMapper(DeptMapper.class); Dept deptInfo = mapper.getDeptAndEmpByDeptIdNew(2); System.out.println(deptInfo); }
|
结果:
分步查询处理一对多
还是分两步,我想想
- 根据部门id查询部门信息
- 根据部门id再去员工信息表中查询所有匹配的员工
第一步的接口:
1 2 3 4 5 6
|
Dept getDeptAndEmpStepOneNew(@Param("deptId") Integer deptId);
|
sql:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <resultMap id="deptAndEmpStepMap" type="Dept"> <id property="deptId" column="dept_id"></id> <result property="deptName" column="dept_name"></result> <collection property="emps" select="com.zzmr.mybatis.mapper.EmpMapper.getDeptAndEmpStepTwoNew" column="dept_id"> </collection> </resultMap>
<select id="getDeptAndEmpStepOneNew" resultMap="deptAndEmpStepMap"> select * from t_dept where dept_id = #{deptId} </select>
|
第一步查询就是根据部门id查询部门信息,而resultMap才是重点,这里使用collection时,也是使用了select指定下一步查询的sql唯一标识,以及传递的参数dept_id
第二步的接口:
1 2 3 4 5 6
|
List<Emp> getDeptAndEmpStepTwoNew(@Param("deptId") Integer deptId);
|
第二步的sql:
1 2 3 4
| <select id="getDeptAndEmpStepTwoNew" resultType="Emp"> select * from t_emp_old where dept_id = #{deptId} </select>
|
这里就是直接使用的resultType,因为查询出的结果就是一个一个的Emp,第二步返回的结果会交给第一步的collection来处理,使多个Emp对象封装为一个List<Emp>
集合,最后再将这个集合赋给emps
再看一下延迟加载的效果:
测试代码:
1 2 3 4 5 6 7
| @Test public void testDeptAndEmpByDeptIdByStepNew() { SqlSession sqlSession = SqlSessionUtil.getSqlSession(); DeptMapper mapper = sqlSession.getMapper(DeptMapper.class); Dept deptInfo = mapper.getDeptAndEmpStepOneNew(2); System.out.println(deptInfo.getDeptName()); }
|
测试结果:
当只获取getDeptName,此时就不涉及到员工的信息,所以只会执行第一条sql语句..
ok,现在应该是比之前好一点了