嘘~ 正在从服务器偷取页面 . . .

Mybtis(六)Mapper级联


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信息会直接映射到Employeedepartment属性里。

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.EmployeeArrayList集合” 。

一般情况下,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级联》


版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Small-Rose / 张小菜 !
评论
  目录