ResultMap关联映射

Endless

所以还是重新学一下这个mybatis这块,sql实在是不会写了…

害,当初没好好学,现在真是追悔莫及


映射关系

表结构:

  1. t_emp_old:
    20230526105411
  2. t_dept:
    20230526105424

当字段名和属性名不一致时,该怎么办?

  1. sql中起别名,比如数据库中是emp_id,而Java属性是empId,那么可以这样写sql:
1
select emp_id empId,emp_name empName ,age,gender from t_emp where emp_id = #{empId};
  1. 给核心配置文件加上如下设置,就可以自定映射下划线和驼峰
1
2
3
4
5
    <settings>
<!-- 将下划线映射为驼峰-->
<setting name="mapUnderscoreToCamelCase" value="true"/>

</settings>

所以当时看这个课的时候,是什么心情呢?

自定义映射resultMap

仔细看看笔记,其实也是挺明白的,就是写的地方优点…

  1. 多对一的映射关系:
    • 级联
    • association
    • 分步查询
  2. 一对多的映射关系:
    • collection
    • 分步查询

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实体类的属性

  1. 在一的一方设置多的一方类型的集合
  2. 在多的一方设置一的一方的类的对象

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);

这个就涉及到两个表关联之间的关系了,什么AB和AB,还有各种各样的关系,害,突然意识到,我可能需要把之前mysql的关联查询也重新学一遍了,这几天就少打点游戏吧,把什么left join,和right join,和inner join都重新看一遍…

mapper映射文件中该怎么写(resultMap和sql)

什么意思呢,就是你看这个方法,是根据员工id查询员工信息已经员工对应的部门信息,而员工类中是有一个部门类的,这时通过sql查询,查询出的dept_iddept_name要映射为一个Dept对象,此时就要使用association来实现了

一共三种方式来处理字段和类对应的关系:

  1. 级联
  2. association
  3. 分布查询
级联处理

什么意思呢,就是单纯使用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>

<!-- Emp getEmpAndDeptByEmpIdNew(@Param("empId") Integer empId);-->
<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.deptIddept.deptName,这就相当于,将查询出的字段dept_id与Emp类中dept属性中的deptId属性进行对应(有点拗口,但是这么说没问题啊没问题),此时得到正确的数据:
20230525234025
不用在意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
<!--    association的方式-->
<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>

<!-- Emp getEmpAndDeptByEmpIdNew(@Param("empId") Integer empId);-->
<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进行查询,重点在于,这个查询应该分为几步,每一步应该干什么

所以,查询员工信息以及员工对应的部门信息,可以分为两步:

  1. 根据员工id查询员工信息
  2. 根据员工对应的部门id在部门表中查询部门信息

第一步的内容:
接口:

1
2
3
4
5
6
/**
* 分步查询员工以及对应的部门信息-step one
* @param empId
* @return
*/
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>

<!-- Emp getEmpAndDeptByStepOneNew(@Param("empId") Integer empId);-->
<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
/**
* 分步查询员工以及所对应员工信息的第二步
* @param deptId
* @return
*/
Dept getEmpAndDeptByStepTwoNew(@Param("deptId") Integer deptId);

sql:

1
2
3
4
5
6
<!--    Dept getEmpAndDeptByStepTwoNew(@Param("deptId") Integer deptId);-->
<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的映射关系了)

查询结果:
20230526090830

那能用一条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:
20230526094226

把延迟加载配置给注掉,再测试,两条sql:
20230526094310

此时就能感受到延迟加载的好处了

但是延迟加载和另外一个属性也有关: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>

<!-- Emp getEmpAndDeptByStepOneNew(@Param("empId") Integer empId);-->
<select id="getEmpAndDeptByStepOneNew" resultMap="empAndDeptOne">
select *
from t_emp_old
where emp_id = #{empId};
</select>

看,此时fetchType为eager,即使开启了延迟加载和关闭了按需加载,依然是执行全部的sql

总结:

  1. 想要实现延迟加载,一个lazyLoadingEnabled=true即可完成,但是老师建议是加上aggressiveLazyLoading=false,这样依然是默认延迟加载
  2. 当配置文件如上面所示,又想要实现立即加载,只需要在associaiton中设置fetchType=eager,即可实现立即加载,而不设置或者是设置fetchType=lazy时,都是延迟加载

ok,多对一搞定了,应该是搞定了,下面继续看一对多

处理一对多映射关系

一共两种方式:

  1. collection
  2. 分步查询
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>集合

20230526103921

看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>

<!-- Dept getDeptAndEmpByDeptIdNew(@Param("deptId") Integer deptId);-->
<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);
}

结果:
20230526105306

分步查询处理一对多

还是分两步,我想想

  1. 根据部门id查询部门信息
  2. 根据部门id再去员工信息表中查询所有匹配的员工

第一步的接口:

1
2
3
4
5
6
/**
* 根据部门id查询部门所对应的所有员工-step1-根据部门id查询部门信息
* @param deptId
* @return
*/
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>

<!-- Dept getDeptAndEmpStepOneNew(@Param("deptId") Integer deptId);-->
<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
/**
* 分布查询-根据部门id查询部门信息以及部门对应的所有员工的信息-step2-根据部门id查询员工信息
* @param deptId
* @return
*/
List<Emp> getDeptAndEmpStepTwoNew(@Param("deptId") Integer deptId);

第二步的sql:

1
2
3
4
<!-- List<Emp> getDeptAndEmpStepTwoNew(@Param("deptId") Integer deptId); -->
<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());
}

测试结果:
20230526120306

当只获取getDeptName,此时就不涉及到员工的信息,所以只会执行第一条sql语句..

ok,现在应该是比之前好一点了