JavaSSM框架-MyBatis 框架(四)
8. 自定义映射resultMap
8.1 环境搭建 & 数据准备
-
数据库数据
CREATE TABLE `t_emp` (`emp_id` int NOT NULL AUTO_INCREMENT,`emp_name` varchar(20),`age` int,`gender` char(1),`dept_id` int,PRIMARY KEY (`emp_id`) );INSERT INTO `t_emp` VALUES (1,'张三',19,'男',1),(2,'李四',34,'男',2),(3,'王五',23,'男',3),(4,'赵六',27,'男',1); CREATE TABLE `t_dept` (`dept_id` int NOT NULL AUTO_INCREMENT,`dept_name` varchar(20),PRIMARY KEY (`dept_id`) );INSERT INTO `t_dept` VALUES (1,'A'),(2,'B'),(3,'C');
-
POJO 类:包
com.qingtian.mybatis.pojo;
-
Emp 类
package com.qingtian.mybatis.pojo;public class Emp {private Integer empId;private String empName;private Integer age;private String gender;public Emp() {}public Emp(Integer empId, String empName, Integer age, String gender) {this.empId = empId;this.empName = empName;this.age = age;this.gender = gender;}public Integer getEmpId() { return empId; }public void setEmpId(Integer empId) { this.empId = empId; }public String getEmpName() { return empName; }public void setEmpName(String empName) { this.empName = empName; }public Integer getAge() { return age; }public void setAge(Integer age) { this.age = age; }public String getGender() { return gender; }public void setGender(String gender) { this.gender = gender; }@Overridepublic String toString() {return "Emp{" +"empId=" + empId +", empName='" + empName + '\'' +", age=" + age +", gender='" + gender + '\'' +'}';} }
-
Dept 类
package com.qingtian.mybatis.pojo;public class Dept {private Integer deptId;private String deptName;public Dept() {}public Dept(Integer deptId, String deptName) {this.deptId = deptId;this.deptName = deptName;}public Integer getDeptId() { return deptId; }public void setDeptId(Integer deptId) { this.deptId = deptId; }public String getDeptName() { return deptName; }public void setDeptName(String deptName) { this.deptName = deptName; }@Overridepublic String toString() {return "Dept{" +"deptId=" + deptId +", deptName='" + deptName + '\'' +'}';} }
-
-
mapper 映射文件:
com.qingtian.mybatis.pojo;
EmpMapper.xml 映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.qingtian.mybatis.mapper.EmpMapper"></mapper>
-
mapper 接口
EmpMapper接口
package com.qingtian.mybatis.mapper;public interface EmpMapper {}
-
mybatis-config.xml核心配置文件:其余部分与模板一样
<typeAliases><package name="com.qingtian.mybatis.pojo"/> </typeAliases><mappers><package name="com.qingtian.mybatis.mapper"/> </mappers>
-
jdbc.properities配置文件及日志配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC jdbc.username=root jdbc.password=123456
-
maven项目目录
后序会在继续添加相关信息,在每节前会说明,如添加DeptMapper映射文件
8.2 字段和属性的映射
为什么需要一致:字段名与属性名不一致时是无法将查询到的一条数据存入实体类对象的,因为其底层机制是通过反射实现的,即Class对象.getDeclaredField(String name) 通过name(在这里即字段名)获得相应的属性
,当属性名与字段名不一致时便无法通过反射得到相应的属性,并为其赋值。
字段名和实体类中的属性名不一致,可以通过三种方法进行处理:
-
为查询的字段
设置别名
,和属性名保持一致
# <!--Emp getEmpByEmpId(@Param("empId") Integer empId);--> <select id="getEmpByEmpId" resultType="Emp">select emp_id as empId,emp_name empName,age,gender from t_emp where emp_id = #{empId} </select>
-
当字段符合MySQL的要求使用时,即
使用下划线命名
,而属性符合java的要求使用驼峰命名
,此时可以在MyBatis的核心配置文件中设置一个全局配置mapUnderscoreToCamelCase
, 可以自动将下划线映射为驼峰
,例如:emp_id --> empId
,emp_name --> empName
<!--MyBatis核心配置文件中,标签的顺序:properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers? --><!--引入配置文件--><properties resource="jdbc.properties"/><settings><!--将下划线映射为驼峰,默认为false,注意书写顺序,写在properties下面--><setting name="mapUnderscoreToCamelCase" value="true"/></settings>
配置该
setting
后,不取别名,只要命名符合规范即可,如emp_id
映射为empId
,而只要java
对象中的属性名为empId
即可 -
使用
resultMap 自定义映射
处理resultMap
:设置自定义的映射关系,通过resultMap设置自定义映射后需要将原来的resultType设置为相应resultMap标签的id
其相关属性如下:
id
:表示该resultMap
自定义映射的唯一标识type
:处理映射关系的实体类的类型
常用标签:
-
id标签
:处理主键
和实体类中属性的映射关系 -
result标签
:处理普通字段
和实体类中属性的映射关系以上两个标签的相关属性如下:
column
:设置映射关系中的字段名,必须是sql查询的表中的某个字段property
:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名
<!--resultMap:设置自定义的映射关系id:唯一标识type:处理映射关系的实体类的类型常用的标签:id:处理主键和实体类中属性的映射关系result:处理普通字段和实体类中属性的映射关系column:设置映射关系中的字段名,必须是sql查询出的某个字段property:设置映射关系中的属性的属性名,必须是处理的实体类类型中的属性名 --> <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> <!--下面的resultMap属性值是写某个resultMap标签的id--> <!--Emp getEmpByEmpId(@Param("empId") Integer empId);--> <select id="getEmpByEmpId" resultMap="empResultMap">select * from t_emp where emp_id = #{empId} </select>
8.3 多对一映射处理
场景模拟:根据员工id查询员工信息以及员工所在的部门信息,即查询员工信息及其部门,即多表联查
数据增加:在pojo包下的Emp类中添加成员变量dept
,并添加相应的get,set方法
,修改toString方法,构造方法可改可不改
private Dept dept;
本节要解决问题:如上在增加Emp类中的成员变量后,如果通过之前学的知识以及相应的映射关系,是无法将查询到的信息赋值给dept对象
的,因为查询的结果是下面这样的:
# 数据库中的多表查询,产生笛卡尔积后用where进行筛选
select * from t_emp,t_dept where t_emp.emp_id = t_dept.dept_id
查询到的结果是dept_id与dept_name,而无法与之Dept dept
对象对应上,以下就是解决该问题的,总共有三种方法:
- 级联方式处理
- association
- 分步查询
8.3.1 级联方式
-
EmpMapper 接口
public interface EmpMapper {// 通过id查询员工与部门信息(多对一,多表查询)Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId); }
-
EmpMapper 映射文件
<resultMap id="empAndDeptResultMap" 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><!--第一种方式,级联将对应的property改为dept.deptId即可--><result column="dept_id" property="dept.deptId"></result><result column="dept_name" property="dept.deptName"></result> </resultMap> <!--这里采用左外连接进行查询,复习一下外连接,其实与多表查询直接where过滤效果一样 --> <!--Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);--> <select id="getEmpAndDeptByEmpId" resultMap="empAndDeptResultMap">select * from t_emp left join t_depton t_emp.dept_id = t_dept.dept_id where t_emp.emp_id = #{empId} </select>
-
Junit 测试
@Test public void testGetEmpAndDeptByEmpId() {SqlSession sqlSession = SqlSessionUtil.getSqlSession();EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);Emp empAndDeptByEmpId = mapper.getEmpAndDeptByEmpId(1);System.out.println(empAndDeptByEmpId); }
8.3.2 association 方式
-
EmpMapper 接口
public interface EmpMapper {// 通过id查询员工与部门信息(多对一多表查询)Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId); }
-
EmpMapper 映射文件
<resultMap id="empAndDeptResultMap" 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><!--第二种方式:associationassociation:处理多对一的映射关系(处理实体类类型的属性)property:设置需要处理映射关系的属性的属性名javaType:设置要处理的属性的类型--><association property="dept" javaType="Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result></association> </resultMap><!--Emp getEmpAndDeptByEmpId(@Param("empId") Integer empId);--> <select id="getEmpAndDeptByEmpId" resultMap="empAndDeptResultMap">select * from t_emp left join t_depton t_emp.dept_id = t_dept.dept_id where t_emp.emp_id = #{empId} </select>
-
Junit 测试
@Test public void testGetEmpAndDeptByEmpId() {SqlSession sqlSession = SqlSessionUtil.getSqlSession();EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);Emp empAndDeptByEmpId = mapper.getEmpAndDeptByEmpId(1);System.out.println(empAndDeptByEmpId); }
8.3.3 分步查询方式
分步查询的基本过程:
第一步:首先根据select * from t_emp where emp_id = #{empId}
SQL语句查询出员工的基本信息,比如查询到的结果如下:
此时,Emp实体类对象中的数据如下:Emp{empId=1, empName='张三', age=19, gender='男', dept=null}
第二步:然后在以查询到的字段作为条件,例如根据字段dept_id
在Dept部门
表中查询相应的部门信息,然后在将查询到的部门的结果赋值给Emp实体类中的Dept对象,最后得到的Emp实体类对象中的数据如下:
Emp{empId=1, empName='张三', age=19, gender='男', dept=Dept{deptId=1, deptName='A'}}
简单说:如下图所示,先执行第一个SQL,然后查出emp相关信息,在根据查询出来的emp中的dept_id,执行第二个SQL查询出对应部门信息,然后赋值给dept对象
分步查询的优点
:可以实现延迟加载
但是必须在核心配置文件中设置全局配置信息:
lazyLoadingEnabled
:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
aggressiveLazyLoading
:开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association和collection中的fetchType属性
设置当前的分步查询是否使用延迟加载
, fetchType="lazy(延迟加载)|eager(立即加载)"
如下是在配置文件中开启延迟加载:
<settings>
<!--将下划线映射为驼峰,默认为false-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启延迟加载,默认为false-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--aggressiveLazyLoading按需加载,默认为false,即默认按需加载注意:如果要开启延迟加载,必须lazyLoadingEnabled设置为true,aggressiveLazyLoading为false如果aggressiveLazyLoading为true,那不管lazyLoadingEnabled设置成什么,都不能开启延迟加载,即aggressiveLazyLoading为false是延迟加载的前提
-->
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
注意:在设置了延迟加载的全局环境中,可以通过设置该属性为eager,取消该部分的延迟加载变成立即加载,但是在没有配置延迟加载全局环境下,即使设置了fetchType="lazy"也不会开启延迟加
-
第一步:查询员工信息
-
EmpMapper 接口
public interface EmpMapper {// 通过分步查询(可理解为子查询)查询员工以及部门信息,第一步Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId); }
-
EmpMapper 映射文件
<resultMap id="empAndDeptByStepResultMap" 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><!--注意这个分步查询就是首先根据getEmpAndDeptByStepOne查询出emp的信息,然后在根据emp中的dept_id通过select="com.qingtian.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"的唯一标识,即namespace.id,把dept_id作为条件查询得到dept对象存进property="dept"中,即Emp对象的dept属性中property:设置需要处理映射关系的属性的属性名,即将select属性中查询的结果赋给该propertyselect:设置分步查询的sqL的唯一标识,即要执行的SQL的namespace.idcolumn:将查询出的某个字段作为分步查询的sql的条件,即select属性中SQL的条件,注意必须是第一步查询出来的字段fetchType:属性值eager(立即加载)/lazy(延迟加载),在设置了延迟加载的全局环境中,可以通过设置该属性为eager,取消该部分的延迟加载变成立即加载,但是在没有配置延迟加载全局环境下,即使设置了fetchType="lazy"也不会开启延迟加载--><association property="dept" fetchType="lazy"select="com.qingtian.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"column="dept_id"></association></resultMap><!--Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);--> <select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">select * from t_emp where emp_id = #{empId} </select>
-
-
第二步:根据查询出的员工的部门id查询部门信息
注意:在MyBatis中最好一个表,对应一个Mapper接口,对应一个Mapper映射文件,即对那张表进行操作就创建一个对应的Mapper接口与映射文件,例如Emp表,EmpMapper接口,EmpMapper映射文件。因此接下来是根据部门id查询部门信息,因此最好新建一个DeptMapper.xml映射文件以及DeptMapper接口,将相应的查询语句写在里面,而不是一起写在EmpMapper映射文件以及EmpMapper接口中。
-
DeptMapper 接口:所在包
com.qingtian.mybatis.mapper
package com.qingtian.mybatis.mapper;public interface DeptMapper {// 分步查询第二步Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId); }
-
DeptMapper.xml 映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.qingtian.mybatis.mapper.DeptMapper"><!--Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);--><!--设置了下划线映射驼峰,直接写resultType即可,如果没设置下划线映射驼峰,则执行后输出为null,即当实体类中一个属性都没有与字段名一致时,输出的对象就是null,而不会输出Dept{deptId=null, deptName=null},如果至少有一个对应上了,即一致,比如deptId对应上了,则输出时为Dept{deptId=1, deptName=null}--><select id="getEmpAndDeptByStepTwo" resultType="Dept"><!-- 这条Sql语句的唯一标识是namespace.id -->select * from t_dept where dept_id = #{deptId}</select> </mapper>
-
-
Junit 测试
@Test public void testGetEmpAndDeptByStep() {SqlSession sqlSession = SqlSessionUtil.getSqlSession();EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);Emp empAndDeptByStepOne = mapper.getEmpAndDeptByStepOne(1);System.out.println(empAndDeptByStepOne); }
测试结果:
Emp{empId=1, empName='张三', age=19, gender='男', dept=Dept{deptId=1, deptName='A'}}
关于延迟加载:
将以上
Junit测试
代码修改成如下代码,即输出查询到的emp的empNam@Test public void testGetEmpAndDeptByStep() {SqlSession sqlSession = SqlSessionUtil.getSqlSession();EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);Emp empAndDeptByStepOne = mapper.getEmpAndDeptByStepOne(1);System.out.println(empAndDeptByStepOne.getEmpName()); }
开启延迟加载后,可以发现第二个
sql语句
并没有执行,因为没用到Emp里面的Dept对象
关闭延迟加载后:
8.4 一对多映射处理
场景模拟:根据部门id查询出部门信息同时查询出属于该部门的员工的所以信息,多表联查
数据增加:在pojo包下的Dept类中添加成员变量emps
,并添加相应的get,set方法
,修改toString方法,构造方法可改可不改,因为是一个部门有0到多个员工,所以是一对多的关系
,所以添加List类型的成员变量
private List<Emp> emps;
本节要解决问题:如在增加了上面的成员变量后,如果想查询时既查询出部门关系,又查询出emps,即部门中的员工信息,则需要掌握一对多映射关系的处理,即如何将查询的结果首先存到Emp对象中,然后在存到List集合中。
以下就是解决该问题的,总共有两种方法:
- collection 标签
- 分步查询
8.4.1 collection 方式
-
DeptMapper 接口
public interface DeptMapper {// 查询部门信息以及该部门所有员工信息Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId); }
-
DeptMapper 映射文件
<!--collection: 处理一对多的映射关系(处理集合类型的属性)注意这里属性用ofType,而不是javaType,javaType是指当前是怎么java类型,而ofType则是指集合中存储的类型 --> <resultMap id="deptAndEmpResultMap" type="Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result><!--ofType:设置集合类型的属性中存储的数据类型--><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><!--注意以下这样写deptId和deptName会报红,这个是因为MyBatisX插件导致报红,运行没问题--><!--<association property="dept" javaType="Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result></association>--></collection> </resultMap> <!--Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId);--> <select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">select * from t_dept left join t_empon t_dept.dept_id = t_emp.dept_id where t_dept.dept_id = #{deptId} </select>
-
Junit 测试
@Test public void testGetDeptAndEmpByDeptId() {SqlSession sqlSession = SqlSessionUtil.getSqlSession();DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);Dept deptAndEmpByDeptId = mapper.getDeptAndEmpByDeptId(1);System.out.println(deptAndEmpByDeptId); }
8.4.2 分步查询方式
分步查询基本过程:与处理多对一关系类似
第一步:首先根据dept_id查询出部门相关信息,例如执行以下sql:
select * from t_dept where dept_id = #{deptId}
第二步:而后在根据dept_id在emp表中(因此该sql最好写到EmpMapper里面
)查询出员工信息,然后在将查询出的结果赋值给Dept对象
中的private List<Emp> emps
属性,因此第二步sql查询的结果应为List集合
,所以EmpMapper接口
中应该写如下函数:返回值为List<Emp>
List<Emp> getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId);
分步查询总结:分步查询即执行两次sql,第一次查询对一张表进行操作,查出相关字段信息,第二次查询则在第一次查询的基础上,以第一次查询出的相关字段信息作为条件,对另一张表进行查询,而后将查询的结果赋值给实体类中的属性。
-
第一步:查询部门相关信息
-
DeptMapper 接口
public interface DeptMapper {// 查询部门相关信息Dept getDeptAndEmpByStepOne(@Param("deptId") Integer deptId); }
-
DeptMapper 映射文件
<resultMap id="deptAndEmpResultMapByStep" type="Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result><!--注意:在分步查询中,这里的property属性的类型,要与select属性中sql语句的唯一标识所对应的sql语句查询出的结果的类型一致,即emps是List<Emp>类型的,则select中的sql语句查询的结果也应该是List<Emp>类型,在第二步中的EmpMapper接口中可以看到--><collection property="emps"select="com.qingtian.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"column="dept_id"></collection> </resultMap><!--Dept getDeptAndEmpByStepOne(@Param("deptId") Integer deptId);--> <select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpResultMapByStep">select * from t_dept where dept_id = #{deptId} </select>
-
-
第二步:根据dept_id查询部门所有人员信息
-
EmpMapper 接口
public interface EmpMapper {// 这里的返回值应该是List<Emp>List<Emp> getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId); }
-
EmpMapper 映射文件
<!--List<Emp> getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId);--> <select id="getDeptAndEmpByStepTwo" resultType="Emp">select * from t_emp where dept_id = #{deptId} </select>
-
-
Junit 测试
@Test public void testGetDeptAndEmpByStep() {SqlSession sqlSession = SqlSessionUtil.getSqlSession();DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);Dept deptAndEmpByStepOne = mapper.getDeptAndEmpByStepOne(1);System.out.println(deptAndEmpByStepOne); }