文章

MyBatis 高级结果映射

MyBatis 的关联关系也被称为高级结果映射。

当实体与实体存在多对一、一对多、多对多三种关系时,MyBatis 提供了多种方法将查询结果组装进实体类对象当中。

假设现有 Employee 和 Department 两个实体类以及对应的 MySQL 表,如下所示:

public class Department {
    private int id;
    private String name;
    private int number;
    
    // 省略 getter 和 setter
}
public class Employee {
    private int id;
    private int number;
    private String name;
    private String gender;
    private int age;
    private Department dep;
    
    // 省略 getter 和 setter
}
CREATE TABLE department
(
	id INT(11) NOT NULL,
    name VARCHAR(255) NOT NULL,
    number INT(11) NOT NULL,
    PRIMARY KEY(id)
);
CREATE TABLE employee
(
	id INT(11) NOT NULL,
    age INT(11) NOT NULL,
    gender VARCHAR(255) NOT NULL,
    name VARCHAR(255) NOT NULL,
    number INT(11) NOT NULL,
    dep_id INT(11) NULL,
    PRIMARY KEY(id)
);

1、多对一

以 Employee 和 Department 两个实体类举例,因 Employee 类有一个 Department 的属性,所以 Employee 实际上和 Department 形成了多对一的关系。当要使用 MyBatis 查询所有的 Employee 对象和每个 包含的 Department 对象时,可以使用别名方法或者定义 ResultMap 方法。

别名方法是对查询的列设置别名,代码如下所示:

<select id="search" resultType="cc.loac.test.entity.Employee">
	SELECT e.*, d.id AS 'dep.id', d.number AS 'dep.number', d.name AS 'dep.name' FROM 
    	employee AS e LEFT JOIN department AS d ON e.dep_id = d.id
</select>

MyBatis 会将 dep.id 对应的值组装到 Employee 实例类 dep 属性的 id 属性上

上面方法虽然方便,但是阅读性不佳。因此 MyBatis 提供了自定义结果集 <resultMap> 标签和多对一 <association> 标签搭配使用的方法。修改后的代码如下所示:

<select id="search" resultMap="EmpAndDep">
	SELECT e.*, d.name AS depName, d.number AS depNumber FROM employee AS e LEFT JOIN department AS d ON e.dep_id = d.id ORDER by e.id
</select>
<resultMap type="cc.loac.test.entity.Employee" id="EmpAndDep">
	<id property="id" column="id" />
    <result property="number" column="number" />
    <result property="name" column="name" />
    <result property="gender" column="gender" />
    <result property="age" column="age" />
    <association property="dep" javaType="cc.loac.test.entity.Department">
    	<id property="id" column="dep_id" />
        <result property="number" column="depNumber" />
        <result property="name" column="depName" />
    </association>
</resultMap>

另外 MyBatis 还提供了一种嵌套查询的方法,代码如下所示:

<select id="search" resultMap="EmpAndDep">
	<!-- 省略查询代码 -->
</select>
<resultMap type="cc.loac.test.entity.Employee" id="EmpAndDep">
	<!-- 省略基本属性 -->
    <association property="dep" column="dep_id" javaType="cc.loac.test.entity.Department"
                 select="cc.loac.test.dao.DepartmentDap.searchById">
    </association>
</resultMap>

事实上这种方法是对 Employee 类型的结果集进行循环,在循环中根据 dep_id 去查询对应的 Department 数据并组装成 Department 类型的对象,再设置到 Employee 对象的 dep 属性中。

该方法避免了 JOIN 查询性能问题,并且适合做懒加载和缓存等场景。

2.一对多

如果在实体类 Department 中新定义一个 List<Employee> 类型的 emps 属性,则 Department 与 Employee 实体类为一对多的关系。

面对一对多的情况,MyBatis 提供了 <resultMap> 标签搭配 <collection> 标签的解决方案。

假如我们现在要查询所有部门,并且要关联各部门所包含的所有员工,代码如下所示:

<select id="searchDepAndEmp" resultMap="DepAndEmp">
	SELECT e.*, d.number AS depNumber, d.name AS depName FROM department AS d LEFT JOIN employee AS e ON d.id = e.dep_id
</select>
<resultMap type="cc.loac.test.entity.Department" id="DepAndEmp">
	<id property="id" column="d_id" />
    <result property="number" column="depNumber" />
    <result property="name" column="depName" />
    <collection property="emps" ofType="cc.loac.test.entity.Employee">
    	<id property="id" column="id" />
        <result property="number" column="number" />
        <result property="name" column="name" />
        <result property="gender" column="gender" />
        <result property="age" column="age" />
    </collection>
</resultMap>

3.多对多

多对多关系在面向对象实体中,其实就是在各方都有对方泛型的集合,从任意一方来看对方都是一对多的关系,所有同样使用一对多的方法进行处理。

License:  CC BY 4.0