MyBatis(七):手动配置映射

前面介绍了 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>

分步查询

  1. 先查员工,得到员工对应的部门 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>
    
  2. 再根据部门 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>

分步查询

类似地,注入集合属性也可以使用分步查询:

  1. 先找部门,得到部门 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>
    
  2. 再根据部门 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 语句。