Mybtis(六)Mapper级联
原本是放在映射器元素resultMap映射结果集的内容,因为篇幅太长,单独另做一篇,查询也方便查找。
Mybatis映射器里的resultMap元素:
1、select 元素
2、resultMap元素
3、insert/update元素
4、delete元素
5、sql元素
5、cache元素
本文主要说明resultMap中级联使用
MyBatis级联主要有:
(1)association:一对一
。
(2)collection:一对多
。
(3)discriminator :鉴别器。
为了演示方便,建两张表,原本想给出建表脚本,奈何系统重装了,数据库没有了。就根据数据库字字段建一些,字段比较少也不难。
一、association关联
association可以说是比较简单的一对一级联。
级联的写法也有不同:
1、级联的嵌套结果映射
级联的嵌套结果映射其实就是直接把结果集映射写在对象中,SQL使用联合查询查出所有映射列。直接看例子:查询员工发时候要求同时查询出所在的部门信息。
java类有两个:
Employee类:
package com.ssm.web.demo.entity.emp;
public class Employee implements java.io.Serializable {
private static final long serialVersionUID = 6654960072154305288L;
private Integer empId;
private String empName;
private String sex;
private String email;
private int did;
private Department department ;
public Employee(){ }
public Employee(Integer empId, String empName, String sex, String email,int did) {
super();
this.empId = empId;
this.empName = empName;
this.sex = sex;
this.email = email;
this.did = did;
}
// getter setter toString ...
}
Department类:
package com.ssm.web.demo.entity.emp;
public class Department implements java.io.Serializable{
private static final long serialVersionUID = 4969741886389350881L;
private Integer deptId;
private String deptName;
// getter setter toString ...
}
XML映射器:
<resultMap id="BaseResultMap" type="com.ssm.web.demo.entity.emp.Employee">
<id column="emp_id" jdbcType="INTEGER" property="empId" />
<result column="emp_name" jdbcType="VARCHAR" property="empName" />
<result column="sex" jdbcType="CHAR" property="sex" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="dept_id" jdbcType="VARCHAR" property="did" />
</resultMap>
<resultMap id="WithDeptResultMap" type="com.ssm.web.demo.entity.emp.Employee">
<id column="emp_id" jdbcType="INTEGER" property="empId" />
<result column="emp_name" jdbcType="VARCHAR" property="empName" />
<result column="sex" jdbcType="CHAR" property="sex" />
<result column="email" jdbcType="VARCHAR" property="email" />
<association property="department" javaType="com.ssm.web.demo.entity.emp.Department">
<result column="dept_id" jdbcType="INTEGER" property="deptId" />
<result column="dept_name" jdbcType="VARCHAR" property="deptName" />
</association>
</resultMap>
很明显要求Employee
对象存在department
类型属性。
如果WithDeptResultMap
的resultMap不想写那么多,也可以使用extends
来减少代码:
<resultMap id="BaseResultMap" type="com.ssm.web.demo.entity.emp.Employee">
<id column="emp_id" jdbcType="INTEGER" property="empId" />
<result column="emp_name" jdbcType="VARCHAR" property="empName" />
<result column="sex" jdbcType="CHAR" property="sex" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="dept_id" jdbcType="VARCHAR" property="did" />
</resultMap>
<resultMap id="WithDeptResultMap" extends="BaseResultMap" type="com.ssm.web.demo.entity.emp.Employee">
<association property="department" javaType="com.ssm.web.demo.entity.emp.Department">
<result column="dept_id" jdbcType="INTEGER" property="deptId" />
<result column="dept_name" jdbcType="VARCHAR" property="deptName" />
</association>
</resultMap>
这样就直接把关联的Department
对象写到Employee
对象中,对应的SELECT
标签:
<select id="selectByPrimaryKeyWithDepart" parameterType="java.lang.Integer" resultMap="WithDeptResultMap">
select
e.emp_id, e.emp_name, e.sex, e.email,d.dept_id,d.dept_name
from tbl_emp e
left join tbl_dept d on e.dept_id=d.dept_id
where emp_id = #{empId,jdbcType=INTEGER}
</select>
这样查询的时候直接查询结果里的Department
信息会直接映射到Employee
的department
属性里。
2、级联的嵌套 Select 查询
关联的嵌套 Select 查询其实就是利用子查询进行结果封装。还是查询员工的例子:
<resultMap id="BaseResultMap" type="com.ssm.web.demo.entity.emp.Employee">
<id column="emp_id" jdbcType="INTEGER" property="empId" />
<result column="emp_name" jdbcType="VARCHAR" property="empName" />
<result column="sex" jdbcType="CHAR" property="sex" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="dept_id" jdbcType="VARCHAR" property="did" />
</resultMap>
<resultMap id="WithDeptResultMap" extends="BaseResultMap" type="com.ssm.web.demo.entity.emp.Employee">
<association property="department" javaType="com.ssm.web.demo.entity.emp.Department" column="dept_id" select="selectDept">
</resultMap>
<select id="selectByPrimaryKeyWithDepart" parameterType="java.lang.Integer" resultMap="WithDeptResultMap">
select
e.emp_id, e.emp_name, e.sex, e.email,e.dept_id
from tbl_emp
where emp_id = #{empId,jdbcType=INTEGER}
</select>
<select id="selectDept" parameterType="java.lang.Integer" resultType="com.ssm.web.demo.entity.emp.Department">
select
*
from tbl_dept
where dept_id = #{dept_id,jdbcType=INTEGER}
</select>
这样就可以通过select
子查询来映射department
的属性了。
这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳。 这个问题被称为“N+1 查询问题”。N+1 查询是这样的:
- 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
- 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。
这个问题会导致成百上千的 SQL 语句被执行。但带来的好处是,MyBatis 能够对这样的查询进行延迟加载,因此可以将大量语句同时运行的开销分散开来。 需要注意的是,如果你加载记录列表之后立刻就遍历列表以获取嵌套的数据,就会触发所有的延迟加载查询,性能可能会变得很糟糕。
所以尽可能根据实际使用场景来决定用哪种方式,在数据列较多或数据集较大时优先使用关联的嵌套结果映射。如果实际需要使用的是一张大表,但是真正使用的并不是所有数据,可以单独定义一个POJO来映射结果集,resultMap
其实可以根据POJO属性来重新定义,也可以不写,因为MybatiS可以根据POJO领域模型,完成自动映射。这样就有了第三种方式,利用POJO别名自动映射。
3、利用POJO别名自动映射。
还是刚才的例子:
先看POJO:
public class EmpInfoVO{
private int empId;
private String empName;
private String sex ;
private String email;
private int did;
private String deptName;
// getter and setter
}
再看XML:
<select id="selectByPrimaryKeyWithDepart" parameterType="java.lang.Integer" resultMap="WithDeptResultMap">
select
e.emp_id as empId,
e.emp_name as empName,
e.sex as sex,
e.email as email,
d.dept_id as did,
d.dept_name as department.deptName
from tbl_emp e
left join tbl_dept d on e.dept_id=d.dept_id
where emp_id = #{empId,jdbcType=INTEGER}
</select>
需要注意的是:别名要和POJO的属性对应。Mybatis默认没有大小写敏感,但是方便阅读尽量和POJO的属性写一致,如果开启了驼峰式命名规范
重新定义POJO的好处是,要使用哪些就直接定义好,不需要查询所有字段出来,对于大表来说还是比较实用的。如果你不确定或就是不想新建POJO或VO来自动映射,非得使用原有的Employee
类,也可以:
<select id="selectByPrimaryKeyWithDepart" parameterType="java.lang.Integer" resultMap="WithDeptResultMap">
select
e.emp_id as empId,
e.emp_name as empName,
e.sex as sex,
e.email as email,
d.dept_id as department.deptId,
d.dept_name as department.deptName
from tbl_emp e
left join tbl_dept d on e.dept_id=d.dept_id
where emp_id = #{empId,jdbcType=INTEGER}
</select>
这样就可以实现和Employee
类属性自动映射了。导航式别名也要求和POJO的属性对应。导航别名department.deptName
是点式分隔,也可以使用下划线分割如:department_deptName
这种写法也可以。
4、关联多结果集
关联的多结果集(ResultSet)
属性 | 描述 |
---|---|
column | 当使用多个结果集时,该属性指定结果集中用于与 foreignColumn 匹配的列(多个列名以逗号隔开),以识别关系中的父类型与子类型。 |
foreignColumn | 指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配。 |
resultSet | 指定用于加载复杂类型的结果集名字。 |
例如
<select id="selectEmployee" resultSets="employees,depts" resultMap="EmpResult" statementType="CALLABLE">
{call getDeptsAndEmplyees(#{id,jdbcType=INTEGER,mode=IN})}
</select>
对应的存储过程里的查询:
SELECT * FROM tbl_dept WHERE dept_id = #{id}
SELECT * FROM tbl_emp WHERE emp_id = #{id}
对应的resultMap
:
<resultMap id="EmpResult" type="Employee">
<id column="emp_id" jdbcType="INTEGER" property="empId" />
<result column="emp_name" jdbcType="VARCHAR" property="empName" />
<result column="sex" jdbcType="CHAR" property="sex" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="dept_id" jdbcType="VARCHAR" property="did" />
<association property="department" javaType="com.ssm.web.demo.entity.emp.Department" resultSet="depts" column="dept_id" foreignColumn="dept_id">
<result column="dept_id" jdbcType="INTEGER" property="deptId" />
<result column="dept_name" jdbcType="VARCHAR" property="deptName" />
</association>
</resultMap>
虽然使用了两个结果集,其实是指定使用 “depts” 结果集的数据来填充 “department” 关联。
二、collection级联
collection级联,集合级联。通常对应的集合类型属性,如List
,Set
等形式的属性。集合元素和关联元素几乎是一样的。
还是用上面的例子,一个员工通常只属于一个部门,那么一个部门可以有很多员工。假设现在需要根据部门查询员工,首先在Department
类中添加员工集合:
public class Department implements java.io.Serializable{
private Integer deptId;
private String deptName;
private List<Employee> empList;
//setter getter tostring ...
}
然后在映射器中映射嵌套结果集合到一个 List 中,可以使用集合元素collection
。
1、集合级联嵌套Select
先看XML
<resultMap id="deptResult" type="com.ssm.web.demo.entity.emp.Department">
<collection property="empList" javaType="ArrayList" column="dept_Id" ofType="com.ssm.web.demo.entity.emp.Employee" select="selectEmpsForDept"/>
</resultMap>
<select id="selectDept" resultMap="deptResult">
SELECT * FROM tbl_dept WHERE dept_Id = #{id}
</select>
<select id="selectEmpsForDept" resultType="com.ssm.web.demo.entity.emp.Employee">
SELECT * FROM tbl_emp WHERE dept_Id = #{id}
</select>
集合元素中有一个新的 “ofType” 属性。这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开来。 所以你可以按照下面这样来阅读映射:
<collection property="empList" javaType="ArrayList" column="dept_Id" ofType="com.ssm.web.demo.entity.emp.Employee" select="selectEmpsForDept"/>
含义是: “empList
是一个存储 com.ssm.web.demo.entity.emp.Employee
的 ArrayList
集合” 。
一般情况下,MyBatis 可以推断 javaType 属性,因此可以不需要填写。所以很多时候你可以简略成:
<collection property="empList" column="dept_Id" ofType="com.ssm.web.demo.entity.emp.Employee" select="selectEmpsForDept"/>
2、集合的嵌套结果映射
集合的嵌套结果映射和级联的嵌套结果映射类似,就是多了一个ofType
属性:
<resultMap id="deptResult" type="com.ssm.web.demo.entity.emp.Department">
<id property="deptId" column="dept_id" />
<result property="deptName" column="dept_name"/>
<collection property="empList" ofType="com.ssm.web.demo.entity.emp.Employee">
<id property="empId" column="emp_id"/>
<result property="empName" column="emp_name"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</collection>
</resultMap>
<select id="selectDepart" parameterType="java.lang.Integer" resultMap="deptResult">
select
d.dept_id as deptId,
d.dept_name as deptName,
e.emp_id as empId,
e.emp_name as empName,
e.sex as sex,
e.email as email
from tbl_dept d
left join tbl_emp e on e.dept_id=d.dept_id
where d.tbl_dept = #{deptId,jdbcType=INTEGER}
</select>
如果希望更细节化、可重用也可以拆开了写:
<resultMap id="deptResult" type="com.ssm.web.demo.entity.emp.Department">
<id property="deptId" column="dept_id" />
<result property="deptName" column="dept_name"/>
<collection property="empList" ofType="com.ssm.web.demo.entity.emp.Employee" resultMap="deptEmpsResult" />
</resultMap>
<resultMap id="deptEmpsResult" type="com.ssm.web.demo.entity.emp.Employee">
<id property="empId" column="emp_id"/>
<result property="empName" column="emp_name"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</resultMap>
需要注意的是我命名的时候没有规范化,如果进行规范化命名,可以指定前缀属性:columnPrefix
<collection property="empList" ofType="com.ssm.web.demo.entity.emp.Employee" resultMap="deptEmpsResult" columnPrefix="Emp_"`/>
这样对应的id="deptEmpsResult"
的resultMap
里的column
的值就可以省略前缀。
3、集合的多结果集
和关联元素那样,我们可以通过执行存储过程实现,它会执行两个查询并返回两个结果集,在映射语句中,必须通过 resultSets
属性为每个结果集指定一个名字,多个名字使用逗号隔开。
<select id="selectDepart" resultSets="depts,emps" resultMap="DeptResult" statementType="CALLABLE">
{call getDeptsAndEmplyees(#{id,jdbcType=INTEGER,mode=IN})}
</select>
对应的存储过程里的查询:
SELECT * FROM tbl_dept WHERE dept_id = #{id}
SELECT * FROM tbl_emp WHERE emp_id = #{id}
对应的resultMap
:
<resultMap id="DeptResult" type="com.ssm.web.demo.entity.emp.Department">
<result column="dept_id" jdbcType="INTEGER" property="deptId" />
<result column="dept_name" jdbcType="VARCHAR" property="deptName" />
<collection property="empList" ofType="com.ssm.web.demo.entity.emp.Employee" resultSet="emps" column="dept_id" foreignColumn="dept_id">
<id column="emp_id" jdbcType="INTEGER" property="empId" />
<result column="emp_name" jdbcType="VARCHAR" property="empName" />
<result column="sex" jdbcType="CHAR" property="sex" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="dept_id" jdbcType="VARCHAR" property="did" />
</collection>
</resultMap>
ofType
的含义是指定 “empList” 集合将会使用存储在 “emps” 结果集中的数据进行填充。
关联或集合的映射,并没有深度、广度或组合上的要求。但在实际使用中映射时要留意性能问题。
三、discriminator 级联
<discriminator javaType="int" column="empName">
<case value="1" resultType="DraftPost"/>
</discriminator>
鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。
有时候,一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。
一个鉴别器的定义需要指定 column 和 javaType 属性。column 指定了 MyBatis 查询被比较值的地方。 而 javaType 用来确保使用正确的相等测试(虽然很多情况下字符串的相等测试都可以工作)。例如:
<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="vanResult"/>
<case value="4" resultMap="suvResult"/>
</discriminator>
</resultMap>
这个示例中,MyBatis 会从结果集中得到每条记录,然后比较它的 vehicle type 值。 如果它匹配任意一个鉴别器的 case,就会使用这个 case 指定的结果映射。 这个过程是互斥的,也就是说,剩余的结果映射将被忽略(除非它是扩展的,我们将在稍后讨论它)。 如果不能匹配任何一个 case,MyBatis 就只会使用鉴别器块外定义的结果映射。 所以,如果 carResult 的声明如下:
<resultMap id="carResult" type="Car">
<result property="doorCount" column="door_count" />
</resultMap>
那么只有 doorCount 属性会被加载。如果希望剩余的属性也能被加载,使用extends
属性可以实现 。
<resultMap id="carResult" type="Car" extends="vehicleResult">
<result property="doorCount" column="door_count" />
</resultMap>
现在 vehicleResult 和 carResult 的属性都会被加载了。
简洁完整写法:
<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultType="carResult">
<result property="doorCount" column="door_count" />
</case>
<case value="2" resultType="truckResult">
<result property="boxSize" column="box_size" />
<result property="extendedCab" column="extended_cab" />
</case>
<case value="3" resultType="vanResult">
<result property="powerSlidingDoor" column="power_sliding_door" />
</case>
<case value="4" resultType="suvResult">
<result property="allWheelDrive" column="all_wheel_drive" />
</case>
</discriminator>
</resultMap>
这些都是结果映射,如果你完全不设置任何的 result 元素,MyBatis 将为你自动匹配列和属性。 需要注意实际使用场景。
相关文章:
文章名称 |
---|
《Mybatis(一)主要组件》 |
《Mybatis(二)配置》 |
《Mybatis(三)动态SQL》 |
《Mybtis(四)工作原理》 |
《Mybtis(五)Mapper映射器》 |
《Mybtis(六)Mapper级联》 |