resultmap解析

265 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情

resultMap

在select标签中有一个非常重要的属性: resultMap ,它是用来自定义结果映射规则的,在今后的MyBatis开发中,我们需要大量使用该标签。

\

还记得最开始的案例中,因为lastName与last_name不一致使得MyBatis无法自动导入属性值,为此,我们开启了MyBatis的驼峰命名,当然,我们也可以自定义结果映射规则来解决这一问题:

<!-- 自定义映射规则 -->
<resultMap id="myEmp" type="com.wwj.mybatis.bean.Employee">
  <!-- 指定主键的封装规则 -->
  <id column="id" property="id"/>
  <!-- 指定非主键的封装规则 -->
  <result column="last_name" property="lastName"/>
</resultMap>
<select id="getEmpById" resultMap="myEmp">
  select *
  from tbl_employee
  where id = #{id}
</select>

首先通过resultMap自定义映射规则,其中id属性为唯一标识,可以随意命名,type为需要指定规则的Bean全类名,然后通过子标签id设置主键的封装规则,通过子标签result设置非主键的封装规则;column为数据表的列名,property为Bean的属性名,作一个一一映射,最后指定resultMap即可。

\

在自定义映射规则中,没有进行映射的其它字段,MyBatis仍然会进行默认的匹配,但推荐只要写了resultMap,就将所有的字段映射都写出来:

<!-- 自定义映射规则 -->
<resultMap id="myEmp" type="com.wwj.mybatis.bean.Employee">
  <!-- 指定主键的封装规则 -->
  <id column="id" property="id"/>
  <!-- 指定非主键的封装规则 -->
  <result column="last_name" property="lastName"/>
  <result column="gender" property="gender"/>
  <result column="email" property="email"/>
</resultMap>

\

resultMap还能够来解决联合查询带来的问题,现在新建一张部门表:

CREATE TABLE tbl_dept(
 id INT(11) PRIMARY KEY AUTO_INCREMENT,
 dept_name VARCHAR(255)
);

INSERT INTO tbl_dept VALUES('开发部');
INSERT INTO tbl_dept VALUES('测试部');

在员工表中新增一列作为外键关联部门表:

ALTER TABLE tbl_employee ADD COLUMN d_id INT(11);

ALTER TABLE tbl_employee ADD CONSTRAINT fk_emp_dept
FOREIGN KEY(d_id) REFERENCES tbl_dept(id);

此时创建Department类:

@Data
@ToString
public class Department {

    private Integer id;
    private String departmentName;
}

修改Employee类:

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Employee {

    private Integer id;
    private String lastName;
    private char gender;
    private String email;
    private Department dept;
}

该如何实现在查询员工的同时查询出该员工所在的部门信息呢?

其实非常简单,首先编写接口方法:

Employee getEmpAndDept(@Param("id") Integer id);

然后编写配置:

<resultMap id="myEmpTo" type="com.wwj.mybatis.bean.Employee">
  <id column="id" property="id"/>
  <result column="last_name" property="lastName"/>
  <result column="gender" property="gender"/>
  <result column="email" property="email"/>
  <!-- 使用级联属性 -->
  <result column="d_id" property="dept.id"/>
  <result column="dept_name" property="dept.departmentName"/>
</resultMap>
<select id="getEmpAndDept" resultMap="myEmpTo">
  SELECT *
  FROM tbl_employee e,
  tbl_dept d
  WHERE e.d_id = d.id
  AND e.id = 1
</select>

通过sql的多表查询获取到两张表的所有字段,然后对这些字段做一一映射即可。

association

除了通过级联属性的方式能够将数据表的数据注入到Department属性中,我们还可以使用 association 标签来完成:

<resultMap id="myEmpTo" type="com.wwj.mybatis.bean.Employee">
        <id column="id" property="id"/>
        <result column="last_name" property="lastName"/>
        <result column="gender" property="gender"/>
        <result column="email" property="email"/>
        <!-- 指定联合的Java对象 -->
        <association property="dept" javaType="com.wwj.mybatis.bean.Department">
            <id column="d_id" property="id"/>
            <result column="dept_name" property="departmentName"/>
        </association>
    </resultMap>

通过association标签指定联合的Java对象,其中property为Bean中的联合对象属性名,javaType为联合对象的类型,在该标签内仍然以同样的方式进行字段和属性的映射,只不过现在映射的是Department类而已。

\

association标签还支持分步查询,我们可以先查出员工信息,再从员工信息中取出部门id,查询部门信息,编写接口方法:

Employee getEmpByIdStep(@Param("id") Integer id);

编写配置:

<resultMap id="myEmpByStep" type="com.wwj.mybatis.bean.Employee">
  <id column="id" property="id"/>
  <result column="last_name" property="lastName"/>
  <result column="gender" property="gender"/>
  <result column="email" property="email"/>
  <!-- 定义联合对象的映射规则 -->
  <association property="dept"
               select="com.wwj.mybatis.mapper.DepartmentMapper.getDeptById"
               column="d_id">
  </association>
</resultMap>
<select id="getEmpByIdStep" resultMap="myEmpByStep">
  SELECT *
  FROM tbl_employee
  WHERE id = #{id};
</select>

这里需要注意的是association标签起的是分布查询的功能,首先通过select标签能够查询出员工信息,其中就有部门的id;property属性指定需要分步查询的对象属性名,select属性指定部门信息的查询方法,column属性指定需要从员工信息中取出d_id字段用于部门查询。

\

最后编写业务代码:

Employee emp = employeeMapper.getEmpByIdStep(1);
System.out.println(emp);

执行结果:

==>  Preparing: SELECT * FROM tbl_employee WHERE id = ?; 
==> Parameters: 1(Integer)
<==    Columns: id, last_name, gender, email, d_id
<==        Row: 1, jack, 1, jack@qq.com, 1
====>  Preparing: select id, dept_name departmentName from tbl_dept where id = ? 
====> Parameters: 1(Integer)
<====    Columns: id, departmentName
<====        Row: 1, 开发部
<====      Total: 1
<==      Total: 1
Employee(id=1, lastName=jack, gender=1, email=jack@qq.com, dept=Department(id=1, departmentName=开发部))

控制台输出两条sql,分布查询完成。

\

分布查询还支持懒加载模式,即:在查询员工信息时并不会查询部门信息,而是当需要使用部门信息时才去查询,大大提升了系统性能,避免了不必要的查询损耗,实现方式如下:

<!-- 开启懒加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 禁用全部加载,设置按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>

只需在全局配置文件中添加这两段配置即可,其中 lazyloadingEnable 是延迟加载开关,设置为true则开启懒加载;而 aggressiveLazyLoading 的值若为true时,则任一方法的调用都会加载该对象的所有延迟加载属性,设置为false则会按需加载,需要注意的是该属性在3.4.1版本及其之前的版本默认值为true,之后的版本默认值为false,所以如果MyBatis版本大于等于3.4.1,则无需设置该属性。

此时若是没有使用到部门信息,则不会查询部门信息:

Employee emp = employeeMapper.getEmpByIdStep(1);
System.out.println(emp.getEmail());

执行结果:

==>  Preparing: SELECT * FROM tbl_employee WHERE id = ?; 
==> Parameters: 1(Integer)
<==    Columns: id, last_name, gender, email, d_id
<==        Row: 1, jack, 1, jack@qq.com, 1
<==      Total: 1
jack@qq.com

collection

现在有一个需求,查询部门id为1的所有员工信息,该如何实现呢?

\

首先在Department类中添加一个集合属性用于存储员工信息:

@Data
@ToString
public class Department {

    private Integer id;
    private String departmentName;
    private List<Employee> emps;
}

然后编写接口方法:

Department getDeptByIdQueryEmps(@Param("id") Integer id);

编写配置,此时我们需要借助collection标签完成业务:

    <resultMap id="MyDept" type="com.wwj.mybatis.bean.Department">
        <id column="did" property="id"/>
        <result column="dept_name" property="departmentName"/>
        <!-- 定义联合集合的映射规则 -->
        <collection property="emps" ofType="com.wwj.mybatis.bean.Employee">
            <id column="eid" property="id"/>
            <result column="last_name" property="lastName"/>
            <result column="gender" property="gender"/>
            <result column="email" property="email"/>
        </collection>
    </resultMap>
    <select id="getDeptByIdQueryEmps" resultMap="MyDept">
        select d.id        did,
               d.dept_name dept_name,
               e.id        eid,
               e.last_name last_name,
               e.gender    gender,
               e.email     email
        from tbl_dept d
                 left join tbl_employee e
                           on d.id = e.id
        where d.id = #{id}
    </select>

首先编写联合查询的sql,其次定义联合集合的映射规则,其中property属性用于指定联合集合的属性名;ofType指定集合存放的元素类型;在collection标签内需要定义集合存放的元素类型的映射规则,最后编写业务代码:

Department department = departmentMapper.getDeptByIdQueryEmps(1);
System.out.println(department);

执行结果:

Department(id=1, departmentName=开发部, emps=[Employee(id=1, lastName=jack, gender=1, email=jack@qq.com, dept=null)])

collection和association一样,也可以进行分步查询,编写接口方法:

Department getDeptByIdQueryEmpsStep(@Param("id") Integer id);

编写配置:

<resultMap id="MyDeptTo" type="com.wwj.mybatis.bean.Department">
  <id column="id" property="id"/>
  <result column="dept_name" property="departmentName"/>
  <collection property="emps"
              select="com.wwj.mybatis.mapper.EmployeeMapper.getEmpsByDeptId"
              column="id">
  </collection>
</resultMap>
<select id="getDeptByIdQueryEmpsStep" resultMap="MyDeptTo">
  select id, dept_name departmentName
  from tbl_dept
  where id = #{id}
</select>

它的用法和association其实是一样的,首先通过select标签查询部门信息,并从中获取部门id,然后查询该部门下的所有员工信息,为此,collection标签在这里起分步查询的作用。其中,property用于指定需要分步查询的属性名;select属性指定需要调用哪个方法进行分步查询;column指定查询得到的部门信息中,采用哪个字段进行分步查询。

\

所以,我们需要提供一个根据部门id查询所有员工的方法:

List<Employee> getEmpsByDeptId(@Param("id") Integer id);
<select id="getEmpsByDeptId" resultType="com.wwj.mybatis.bean.Employee">
  select *
  from tbl_employee
  where d_id = #{id}
</select>

最后编写业务代码:

Department department = departmentMapper.getDeptByIdQueryEmpsStep(1);
System.out.println(department);

执行结果:

Department(id=1, departmentName=开发部, emps=[Employee(id=1, lastName=null, gender=1, email=jack@qq.com, dept=null)])

当然了,collection标签也是支持懒加载的,同样在全局配置文件进行配置即可。

\

有时候进行分步查询,我们需要传递多个字段值,这个时候我们只需将参数值封装成Map并赋值给column属性即可,比如:

<collection property="emps"
            select="com.wwj.mybatis.mapper.EmployeeMapper.getEmpsByDeptId"
            column="{id=id,dept_name=dept_name}">
</collection>

其中的键为调用的分步方法所需的属性名,而值为第一次查询得到的即将用于分步查询的字段名。

discriminator

该标签用于配置鉴别器,它可以根据某列的值进行改变封装行为,比如这样的一个需求:

\

根据id查询员工,若查询得到的是女员工,则查询出部门信息;否则不查询:

<resultMap id="MyEmpDis" type="com.wwj.mybatis.bean.Employee">
  <id column="id" property="id"/>
  <result column="last_name" property="lastName"/>
  <result column="gender" property="gender"/>
  <result column="email" property="email"/>
  <!-- 配置鉴别器,根据gender封装 -->
  <discriminator javaType="java.lang.String" column="gender">
    <!-- 为女生,查询部门信息 -->
    <case value="0" resultType="com.wwj.mybatis.bean.Employee">
      <association property="dept"
                   select="com.wwj.mybatis.mapper.DepartmentMapper.getDeptById"
                   column="d_id">
      </association>
    </case>
    <!-- 为男生,不作另外处理 -->
    <case value="1" resultType="com.wwj.mybatis.bean.Employee">
    </case>
  </discriminator>
</resultMap>

首先封装id、姓名、性别和邮箱属性,然后配置鉴别器,其column指定需要以哪一列作为鉴别依据,javaType为该列的类型,在discriminator标签中通过case子标签进行判断,value指定列的值,resultType为该条件下得到的结果类型,需要注意的是,即使case标签中没有任何内容,resultType属性也是不可缺少的。