前面介绍了 mapper 获取参数和返回值的方式,但是在实际开发中,查询出的字段和想注入的属性的对应关系往往没有那么简单,一对多、多对一的映射关系比比皆是,即便是一一对应,也存在字段名称和属性名称不一致的情况,这些情况都需要手动配置映射关系。
字段名和属性名不一致的情况
有时,数据库表中的字段名与实体类的属性名不一致,例如后者使用了驼峰,前者使用下划线。由于底层使用的是反射实现属性赋值,将找不到属性与字段名的映射关系。下面给出几种解决方法:
设置字段别名
在写 SQL 语句时,设置字段别名与实体类属性一致。
<select id="getEmpById" resultType="Emp">
select emp_id empId, emp_name empName, age from t_emp where emp_id=#{empId}
</select>
设置全局配置
如果字段名符合 MySQL 的要求使用下划线,属性符合 Java 的要求使用驼峰,并且字段与属性一一对应,可以在 MyBatis 核心配置文件中,配置 settings 全局配置。
<settings>
<!-- 配置下划线和驼峰之间的映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
更多全局配置属性,可以参见 MyBatis 手册的 Configuration XML 章节。
自定义映射
可以使用 resultMap 实现自定义映射。
<resultMap id="empResultMap" type="site.penghao.mybatis.pojo.Emp">
<!-- 设置主键与实体类属性的映射关系 -->
<id column="emp_id" property="empId"/>
<!-- 设置普通字段与实体类属性的映射关系 -->
<result column="emp_name" property="empName"/>
<result column="age" property="age"/>
</resultMap>
<!-- 绑定 resultMap -->
<select id="getEmpById" resultMap="empResultMap">
select * from t_emp where id = #{empId}
</select>
注入对象属性的情况
有时我们查出的表中的字段和实体类的属性并不是一一对应的,例如:将多表联查查出的员工-部门信息表注入到员工实体类中,实体类有一个部门类成员,但部门有多个字段信息。
级联映射
resultMap 允许对属性进行级联赋值。
<resultMap id="empResultMap" type="site.penghao.mybatis.pojo.Emp">
<!-- 设置主键与实体类属性的映射关系 -->
<id column="emp_id" property="empId"/>
<!-- 设置普通字段与实体类属性的映射关系 -->
<result column="emp_name" property="empName"/>
<result column="age" property="age"/>
<!-- 属性级联赋值 -->
<result column="dept_id" property="dept.deptId"/>
<result column="dept_name" property="dept.deptName"/>
</resultMap>
配置 association 标签
association 是专门处理多对一关系的标签(也兼容一对一),只要是实体类型的属性,都可以使用 association 处理。
<resultMap id="empResultMap" type="site.penghao.mybatis.pojo.Emp">
<!-- 设置主键与实体类属性的映射关系 -->
<id column="emp_id" property="empId"/>
<!-- 设置普通字段与实体类属性的映射关系 -->
<result column="emp_name" property="empName"/>
<result column="age" property="age"/>
<!-- 实体类属性的 association 赋值 -->
<association property="dept" javaType="site.penghao.mybatis.pojo.Dept">
<id column="dept_id" property="deptId"/>
<result column="dept_name" property="deptName"/>
</association>
</resultMap>
分步查询
-
先查员工,得到员工对应的部门 id
<resultMap id="empResultMap" type="Emp"> <id column="eid" property="eid"></id> <result column="ename" property="ename"></result> <result column="age" property="age"></result> <!-- select:设置分步查询,查询某个属性的值的 sql 的标识(namespace.id) column:将 sql 以及查询结果中的某个字段设置为分步查询的条件(参数) 「以 column 作为参数,调用 select 对应的查询方法,获得的返回值注入 property」 --> <association property="dept" select="site.penghao.mybatis.mapper.DeptMapper.getDeptById" column="dept_id" /> </resultMap> <select id="getEmpById" resultMap="empResultMap"> select * from t_emp where emp_id = #{empId} </select>
-
再根据部门 id 查找部门信息
<select id="getDeptById" resultType="Dept"> select * from t_dept where dept_id = #{deptId} </select>
分步查询的优势在于可以实现「延迟加载」或者说「懒加载」,在核心配置文件中设置全局配置 settings:
- lazyLoadingEnabled:开启延迟加载,开启时,所有关联对象都会延迟加载,即只执行分步查询的第一步。默认关闭。
- aggressiveLazyLoading:开启主动延迟加载,开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性只有被需要的时候才被加载。默认关闭。
因此,我们开启延迟加载一般需要将 lazyLoadingEnabled 开启,aggressiveLazyLoading 关闭:
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
由于前面的配置是全局生效的,如果需要给特定的某个分步查询配置立即/全局查询,可以配置 association 标签的 fetchType 属性为 eager(立即加载) / lazy(延迟加载)。
<!-- 取消延迟加载 -->
<association ... fetchType="eager">
...
</association>
注入集合属性的情况
例如我们需要根据部门的 id 查询该部门信息以及该部门的所有员工的 id 信息,经过多表联查,一个部门将会有多条员工记录,每条员工记录对应一个员工。我们希望将这些员工信息都放在同一个集合 emps 中。
配置 collection 标签
collection 标签和 association 标签极度相似,不过 association 标签配置目标对象用的是 javaType 属性,collection 标签配置集合泛型用的是 ofType 属性。
<resultMap id="deptEmpMap" type="Dept">
...
<!--
ofType:设置 collection 标签所处理的集合属性的泛型
-->
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="ename" column="ename"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</collection>
</resultMap>
<select id="getDeptEmpById" resultMap="deptEmpMap">
select * from t_dept dept
left join t_emp emp
on dept.dept_id=emp.dept_id
where dept.dept_id = #{deptId}
</select>
分步查询
类似地,注入集合属性也可以使用分步查询:
-
先找部门,得到部门 id
<resultMap id="deptResultMap" type="Dept"> ... <collection property="emps" select="site.penghao.mybatis.mapper.EmpMapper.getEmpListByDeptId" column="dept_id" /> </resultMap> <select id="getDeptById" resultMap="deptResultMap"> select * from t_dept where dept_id = #{deptId} </select>
-
再根据部门 id 在员工表找员工信息
<!--List<Emp> getEmpListByDeptId(@Param("deptId") int deptId);--> <select id="getEmpListByDeptId" resultType="Emp"> select * from t_emp where dept_id = #{deptId} </select>
小结
- 通过配置 resultMap,可以自定义字段和属性的映射关系;
- resultMap 中的属性支持级联赋值,也可以通过配置 association 和 javaTppe,给实体属性赋值;
- 使用 collection 可以给集合中的所有元素赋值,ofType 设定的是集合属性的泛型;
- association 和 collection 都支持分步查询,允许设定 select 语句。