Wenqi's Blog

MyBatis之关联映射解析

ORM框架一个重要的技术点是处理对象间的关联映射,比如一对一,一对多的关系,和Hibernate不同的是,Mybatis的使用需要开发人员直接和SQL语句进行打交道,所以在处理关联映射的时候不论是文件配置还是实现原理都是大有不同的,本文致力于使用一个例子讲清楚不同关联映射关系的配置和使用方法,以及作者在使用过程中对不同的参数的作用的深入理解。

对象关系

我们有这样的一组对象关系,简单来说,一个雇员(Employee)有一个工卡(WorkCard),有多个员工任务(EmployeeTask),员工任务仅包含一个任务(Task),对于员工来还有体检表(HealthForm),但是由于有体检的内容男女有别,所以我们从Employee继承出来FemaleEmployee和MaleEmpolyee,分别包含FemaleHealthForm和MaleHealthForm。

从表的结构来看,其关键的外键关系如下

Mybatis配置

我们从简单的EmployeeTask和Task之间的一对一关系来说,无论是从Java Bean的角度来看还是从表的关系来看,都是EmployeeTask在维护关联关系。我们所说的关联关系的使用更多的是在查询的时候,也就是在查询的时候能够以级联的查询出关联的对象。对于插入的时候,因为Mybatis的实现原理是开发人员自定义SQL语句,所以不能实现级联的插入,我们只需要提供一个关联对象相关联的外键,就像在插入EmployeeTask的时候如何处理Task对象这个属性这样(点击这里

一对一查询

所以我们接下来更加着重考虑在查询操作(select)的时候的级联映射的配置,也就是对select后的resultMap如何配置(resultMap是描述从数据库中查询出的数据列和pojo的属性之间如何映射,如果列名和属性名一致的可以不用配置,会进行自动映射)。 拿EmployeeTask来说,对于其查询语句我们有

1
2
3
<select id="getEmployeeTaskByEmpId" parameterType="long" resultMap="empTaskResultMap">
select * from t_employee_task where emp_id=#{empId}
</select>

其中empTaskResultMap的配置如下

1
2
3
4
5
6
7
<resultMap id="empTaskResultMap" type="multiAssociation.pojo.EmployeeTask">
<id column="id" property="id"></id>
<result column="emp_id" property="empId"/>
<result column="task_name" property="taskName"/>
<association column="task_id" property="task" select="multiAssociation.pojo.TaskMapper.getTaskById"/>
<!-- select语句对应的是对应mapper xml中方法的id-->
</resultMap>

这里面我们对列名和属性名不一致的情况进行了配置,并且使用到了association标签,这个标签是用来关联一个对象的,解读一下这个语句的意思就是:将列task_id的值左为参数传入multiAssociation.pojo.TaskMapper.getTaskById作为参数查询后作为task属性的值,其中multiAssociation.pojo.TaskMapper.getTaskById是TaskMapper配置文件中指定的查询语句,这种方式称为嵌套查询,所以TaskMapper中必须有对应的方法才可以,也就是下面的配置

1
2
3
<select id="getTaskById" parameterType="long" resultType="multiAssociation.pojo.Task">
select * from t_task where id=#{id}
</select>

当然我们也可以通过另一种方式,就是在getEmployeeTaskByEmpId的select语句中使用连接查询,然后将对应的task列对应的值映射到对应的属性上也是可以的

一对多映射

一对多映射使用collection标签,它的使用方法和association是一样的,我们使用Employee来做为示例,它有一个一对一关系和一个一对多的关系,它的resultMap配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<resultMap id="employeeResultMap" type="multiAssociation.pojo.Employee">
<id column="id" property="id"></id>
<result column="real_name" property="realName"></result>
<result column="sex" property="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
<result column="POSITION" property="position"/>
<association property="workCard" column="id" select="multiAssociation.pojo.WorkCardMapper.getWorkCardByEmpId"/>
<collection property="employeeTaskList" column="id" select="multiAssociation.pojo.EmployeeTaskMapper.getEmployeeTaskByEmpId"/>
<discriminator javaType="long" column="sex">
<!--最后返回的resultMap会取代原来的resultMap,所以后面的的resultMap要extends-->
<case value="0" resultMap="femaleEmployeeResultMap"></case>
<case value="1" resultMap="maleEmployeeFormResultMap"/>
</discriminator>
</resultMap>

可以看到association标签和collection标签的使用几乎一模一样,那么它们对于select标签使用的嵌套方法有没有什么特殊的要求,答案是没有的,这里嵌套的select依赖于对应的Mapper的xml文件配置的,而不依赖于具有明确返回类型(单个对象或者对象列表)Mapper接口方法,从这个角度来说,xml配置问题不是强依赖于接口的。直观的来说multiAssociation.pojo.EmployeeTaskMapper.getEmployeeTaskByEmpId虽然是配置到collection的嵌套查询中,但是在对应的Mapper接口中其返回值可能仅仅是一个对象。
另外大家可能注意到了discriminator标签,这段配置的意思根据查询结果的sex列的值,如果sex为0,则返回femaleEmployeeResultMap作为最终结果,如果是1,则返回maleEmployeeFormResultMap作为结果,当然,我们也需要femaleEmployeeResultMap和maleEmployeeFormResultMap继承上面employeeResultMap的基础属性,这个使用extends来实现,从而实现了有判别的返回类对象。

1
2
3
4
5
6
7
<resultMap id="femaleEmployeeResultMap" type="multiAssociation.pojo.FemaleEmployee" extends="employeeResultMap">
<association property="femaleHealthForm" column="id" select="multiAssociation.pojo.FemaleHealthFormMapper.getFemaleHealthFormWithEmpId"/>
</resultMap>
<resultMap id="maleEmployeeFormResultMap" type="multiAssociation.pojo.MaleEmployee" extends="employeeResultMap">
<association property="maleHealthForm" column="id" select="multiAssociation.pojo.MaleHealthFormMapper.getMaleHealthFormWithEmpId"/>
</resultMap>

关于源码

你可以在这里找到本文的所有源码,本文发表于我的博客,欢迎关注!

参考文档

Mapper-XML文件 Result_Maps

坚持原创技术分享,您的支持将鼓励我继续创作!