您好,欢迎来到测品娱乐。
搜索
您的当前位置:首页【MyBatis】高级结果映射

【MyBatis】高级结果映射

来源:测品娱乐


一、联合查询

pojo对象是我们用来和数据库表进行映射的对象,它的字段应该完全包含了一个数据库表的全部字段。但是有的时候我们查询的结果并不需要显示全部字段,或者我们需要将A表和B表进行联合查询,查询的结果既有A表的字段,又有B表的字段,这个时候再继续用pojo来作为数据库数据传输的对象显然就不太合适了,我们就需要根据查询结果,再创建一个DTO对象来帮助我们进行数据传输。

 

emp.java

package cn.tulingxueyuan.pojo;
import java.time.LocalDate;

public class Emp {
    private Integer id;
    private String username;
    private LocalDate createDate;
    private Integer deptId;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public LocalDate getCreateDate() {
        return createDate;
    }
    public void setCreateDate(LocalDate createDate) {
        this.createDate = createDate;
    }
    public Integer getDeptId() {
        return deptId;
    }
    public void setDeptId(Integer deptId) {
        this.deptId = deptId;
    }
    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", createDate=" + createDate +
                ", deptId=" + deptId+
                '}';
    }
}

上面是Emp表对应的pojo对象,包含了Emp表的全部字段,但是现在我们需要进行一个Emp表和DEPT表的联合查询,再去使用这个Emp对象显然就不合适了,需要根据这个联合查询显示的字段,再去创建一个DTO对象。

QueryEmpDTO

package cn.tulingxueyuan.pojo;

public class QueryEmpDTO {
    private String deptName;
    private Integer deptId;
    private Integer id;
    private String username;

    public String getDeptName() {
        return deptName;
    }
    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }
    public Integer getDeptId() {
        return deptId;
    }
    public void setDeptId(Integer deptId) {
        this.deptId = deptId;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    @Override
    public String toString() {
        return "QueryEmpDTO{" +
                "deptName='" + deptName + '\'' +
                ", deptId=" + deptId +
                ", id=" + id +
                ", username='" + username + '\'' +
                '}';
    }
}

这个DTO对象就既包含了EMP表的字段,也包含了DEPT表的字段,我们就可以用这个对象进行数据传输。

 

 

下面来设置映射关系。

EmpMapper.xml

<!-- 实现表联结查询的方式:  可以映射: DTO -->
<resultMap id="QueryEmp_Map" type="QueryEmpDTO">
    <!-- 主键字段的映射关系 -->
    <id column="e_id" property="id"></id>
    <!-- 其他字段的映射关系 -->
    <result column="user_name" property="username"></result>
    <result column="d_id" property="deptId"></result>
    <result column="dept_name" property="deptName"></result>
</resultMap>
<!-- resultMap要和上面<resultMap>标签中的id属性一致,这样就能将映射关系关联到这个select标签的结果集上 -->
<select id="QueryEmp"  resultMap="QueryEmp_Map">
    select t1.id as e_id,t1.user_name,t2.id as d_id,t2.dept_name from emp t1
    INNER JOIN dept t2 on t1.dept_id=t2.id
    where t1.id=#{id}
</select>

上面这段配置,就是用了我们建立的DTO对象来获取从数据库中查询的数据。

 

 

如果不想用DTO对象,也可以使用Map对象来接收查询结果。MyBatis会自动将查询出来的列名存到Key中,列值存到value里。

<!-- 实现表联结查询的方式:  可以映射map -->
<resultMap id="QueryEmp_Map" type="map">
    <id column="e_id" property="id"></id>
    <result column="user_name" property="username"></result>
    <result column="d_id" property="deptId"></result>
    <result column="dept_name" property="deptName"></result>
</resultMap>
<select id="QueryEmp"  resultMap="QueryEmp_Map">
    select t1.id as e_id,t1.user_name,t2.id as d_id,t2.dept_name from emp t1
    INNER JOIN dept t2 on t1.dept_id=t2.id
    where t1.id=#{id}
</select>

Test

@Test
public void test01() {
    try(SqlSession sqlSession = sqlSessionFactory.openSession()){
        // MyBatis在getMapper就会给我们创建jdk动态代理
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        QueryEmpDTO dto = mapper.QueryEmp(4);
        System.out.println(dto);
    }
}

上面是联合查询时最简单的写法,下面我们讲一下关联查询一些更复杂的情况。

在关系型数据库中,我们经常要处理一对一 、 一对多的关系 。 例如, 一辆汽车需要有一个引擎,这是一对一的关系。 一辆汽车有 4 个或更多个轮子,这是一对多的关系 。关联元素就是专门用来处理关联关系的;

关联元素:

  • association      一对一关系或多对一
  • collection        一对多关系

关联方式:

  • 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集
  • 嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型

下面我们分别讲解嵌套结果和嵌套查询。

 

 

二、嵌套结果

2.1 多对一

有的时候查询结果的对象中还会包含着另一个对象,这个时候就需要用嵌套映射来解决这个问题,避免映射配置的冗余。

Emp.java

package cn.tulingxueyuan.pojo;
import java.time.LocalDate;

public class Emp {
    private Integer id;
    private String username;
    private LocalDate createDate;
    // Emp对象中还包含了一个Dept类型的属性
    private Dept dept;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public LocalDate getCreateDate() {
        return createDate;
    }
    public void setCreateDate(LocalDate createDate) {
        this.createDate = createDate;
    }
    public Dept getDept() {
        return dept;
    }
    public void setDept(Dept dept) {
        this.dept = dept;
    }
    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", createDate=" + createDate +
                ", dept=" + dept +
                '}';
    }
}

EmpMapper.xml

<!--嵌套结果   多 对 一  -->
<resultMap id="QueryEmp_Map2" type="Emp">
    <id column="e_id" property="id"></id>
    <result column="user_name" property="username"></result>
    <!--
        association 实现多对一中的  “一”
            property 指定对象中的嵌套对象属性
    -->
    <association property="dept">
        <id column="d_id" property="id"></id>
        <id column="dept_name" property="deptName"></id>
    </association>
</resultMap>
<select id="QueryEmp2"  resultMap="QueryEmp_Map2">
    select t1.id as e_id,t1.user_name,t2.id as d_id,t2.dept_name from emp t1
    INNER JOIN dept t2 on t1.dept_id=t2.id
    where t1.id=#{id}
</select>

 

 

EmpMapper.java

package cn.tulingxueyuan.mapper;
import cn.tulingxueyuan.pojo.Emp;
import cn.tulingxueyuan.pojo.QueryEmpDTO;
import java.util.Map;

public interface EmpMapper {
    /*徐庶老师实际开发中的实现方式*/
    QueryEmpDTO QueryEmp(Integer id);
    /*实用嵌套结果实现联合查询  多对一 */
    Emp QueryEmp2(Integer id);

    /*实用嵌套查询实现联合查询  多对一 */
    Emp QueryEmp3(Integer id);
}

 

 

总结一下嵌套结果时association标签的用法

association标签 嵌套结果方式常用属性:

  • property :对应实体类中的属性名,必填项。
  • javaType : 属性对应的 Java 类型 。
  • resultMap : 可以直接使用现有的 resultMap ,而不需要在这里配置映射关系。
  • columnPrefix :查询列的前缀,配置前缀后,在子标签配置 result 的 column 时可以省略前缀

 

Tips:

  • resultMap可以通过使用extends实现继承关系,简化很多配置工作量;
  • 关联的表查询的类添加前缀是编程的好习惯;
  • 通过添加完整的命名空间,可以引用其他xml文件的resultMap;

 

 

 

2.2 一对多

Dept.java

package cn.tulingxueyuan.pojo;
import java.util.List;

public class Dept {
    private Integer id;
    private String deptName;
    // Dept中包含了一个Emp集合
    private List<Emp> emps;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getDeptName() {
        return deptName;
    }
    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }
    public List<Emp> getEmps() {
        return emps;
    }
    public void setEmps(List<Emp> emps) {
        this.emps = emps;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "id=" + id +
                ", deptName='" + deptName + '\'' +
                ", emps=" + emps +
                '}';
    }
}

DeptMapper.xml

<!-- 嵌套结果: 一对多  查询部门及所有员工 -->
<resultMap id="SelectDeptAndEmpsMap" type="Dept">
    <id column="d_id"  property="id"></id>
    <id column="dept_name"  property="deptName"></id>
    <!--
    <collection>  映射一对多中的 “多”
        property 指定需要映射的“多”的属性,一般声明为List
        ofType  需要指定list的类型
    -->
    <collection property="emps" ofType="Emp" >
        <id column="e_id" property="id"></id>
        <result column="user_name" property="username"></result>
        <result column="create_date" property="createDate"></result>
    </collection>
</resultMap>

<select id="SelectDeptAndEmps" resultMap="SelectDeptAndEmpsMap">
    select t1.id as d_id,t1.dept_name,t2.id e_id,t2.user_name,t2.create_date from dept t1
    LEFT JOIN emp t2 on t1.id=t2.dept_id
    where t1.id=#{id}
</select>

 

 

DeptMapper.java

package cn.tulingxueyuan.mapper;

import cn.tulingxueyuan.pojo.Dept;

public interface DeptMapper {
    //嵌套查询: 一对多   使用部门id查询员工
   Dept SelectDeptAndEmps(Integer id);
   // 嵌套查询(异步查询): 一对多  查询部门及所有员工
    Dept SelectDeptAndEmps2(Integer id);
}

 

collection标签总结

  • collection 支持的属性以及属性的作用和 association 完全相同
  • mybatis会根据id标签,进行字段的合并,合理配置好ID标签可以提高处理的效率;

 

Tips:

如果要配置一个相当复杂的映射,一定要从基础映射开始配置,每增加一些配置就进行对应的测试,在循序渐进的过程中更容易发现和解决问题 。

 

 

 

三、嵌套查询

在上述逻辑的查询中,是由我们自己来完成sql语句的关联查询的,那么我们能让MyBatis帮我们实现自动的关联查询吗?

3.1 多对一

EmpMapper.xml

<!--嵌套查询(分步查询)   多 对 一
  联合查询和分步查询区别:   性能区别不大
                            分部查询支持 懒加载(延迟加载)
   需要设置懒加载,一定要使用嵌套查询的。
   要启动懒加载可以在全局配置文件中设置 lazyLoadingEnabled=true
   还可以单独为某个分步查询设置立即加载 <association fetchType="eager"
  -->
<resultMap id="QueryEmp_Map3" type="Emp">
    <id column="id" property="id"></id>
    <result column="user_name" property="username"></result>
    <!-- association 实现多对一中的  “一”
        property 指定对象中的嵌套对象属性
        column  指定将哪个字段传到分步查询中
        select 指定分步查询的 命名空间+ID
        以上3个属性是实现分步查询必须的属性
        fetchType 可选, eager|lazy   eager立即加载   lazy跟随全局配置文件中的lazyLoadingEnabled
     -->
    <association property="dept"    column="dept_id"  select="cn.tulingxueyuan.mapper.DeptMapper.SelectDept">
    </association>
</resultMap>

<select id="QueryEmp3"  resultMap="QueryEmp_Map3">
   select  * from emp where id=#{id}
</select>

 

DeptMapper.xml

<!-- 根据部门id查询部门-->
<select id="SelectDept" resultType="dept">
    SELECT * FROM dept where id=#{id}
</select>

 

 

总结一下嵌套查询时association标签的用法

association标签 嵌套查询方式常用属性:

  • select :另 一个映射查询的 id, MyBatis 会额外执行这个查询获取嵌套对象的结果 。
  • column :列名(或别名),将主查询中列的结果作为嵌套查询的 参数。
  • fetchType :数据加载方式,可选值为 lazy 和 eager,分别为延迟加载和积极加载 ,这个配置会覆盖全局的lazyLoadingEnabled 配置;

 

TipsN+1 查询问题

概括地讲,N+1 查询问题可以是这样引起的:

  • 你执行了一个单独的 SQL 语句来获取结果列表(就是“+1”)。
  • 对返回的每条记录,你执行了一个查询语句来为每个加载细节(就是“N”)。

这个问题会导致成百上千的 SQL 语句被执行。这通常不是期望的。

解决办法:使用“fetchType=lazy”并且全局setting进行改善:

<setting name="aggressiveLazyLoading" value="false"/>

延迟加载会在下一章节会详细讲解:四、延迟查询

 

 

3.2 一对多

DeptMapper.xml

<!-- 嵌套查询(异步查询): 一对多  查询部门及所有员工 -->
<resultMap id="SelectDeptAndEmpsMap2" type="Dept">
    <id column="d_id"  property="id"></id>
    <id column="dept_name"  property="deptName"></id>
    <!--
    <collection  映射一对多中的 “多”
        property 指定需要映射的“多”的属性,一般声明为List
        ofType  需要指定list的类型
        column 需要将当前查询的字段传递到异步查询的参数
        select 指定异步查询
    -->
    <collection property="emps" ofType="Emp" column="id" select="cn.tulingxueyuan.mapper.EmpMapper.SelectEmpByDeptId" >
    </collection>
</resultMap>
<select id="SelectDeptAndEmps2" resultMap="SelectDeptAndEmpsMap2">
    SELECT * FROM dept where id=#{id}
</select>

 

EmpMapper.xml

<!-- 根据部门id所有员工 -->
<select id="SelectEmpByDeptId"  resultType="emp">
    select  * from emp where dept_id=#{id}
</select>

 

Emp.java

package cn.tulingxueyuan.pojo;
import java.time.LocalDate;

public class Emp {
    private Integer id;
    private String username;
    private LocalDate createDate;
    private Dept dept;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public LocalDate getCreateDate() {
        return createDate;
    }
    public void setCreateDate(LocalDate createDate) {
        this.createDate = createDate;
    }
    public Dept getDept() {
        return dept;
    }
    public void setDept(Dept dept) {
        this.dept = dept;
    }
    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", createDate=" + createDate +
                ", dept=" + dept +
                '}';
    }
}

 

Dept.java:

package cn.tulingxueyuan.pojo;
import java.util.List;

public class Dept {
    private Integer id;
    private String deptName;
    private List<Emp> emps;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getDeptName() {
        return deptName;
    }
    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }
    public List<Emp> getEmps() {
        return emps;
    }
    public void setEmps(List<Emp> emps) {
        this.emps = emps;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "id=" + id +
                ", deptName='" + deptName + '\'' +
                ", emps=" + emps +
                '}';
    }
}

 

EmpMapper.java

package cn.tulingxueyuan.mapper;
import cn.tulingxueyuan.pojo.Emp;
import cn.tulingxueyuan.pojo.QueryEmpDTO;
import java.util.Map;

public interface EmpMapper {
    /*徐庶老师实际开发中的实现方式*/
    QueryEmpDTO QueryEmp(Integer id);
    /*实用嵌套结果实现联合查询  多对一 */
    Emp QueryEmp2(Integer id);

    /*实用嵌套查询实现联合查询  多对一 */
    Emp QueryEmp3(Integer id);
}

 

DeptMapper.java

package cn.tulingxueyuan.mapper;
import cn.tulingxueyuan.pojo.Dept;

public interface DeptMapper {
    //嵌套查询: 一对多   使用部门id查询员工
   Dept SelectDeptAndEmps(Integer id);
   // 嵌套查询(异步查询): 一对多  查询部门及所有员工
    Dept SelectDeptAndEmps2(Integer id);
}

 

四、延迟查询

4.1 什么是延迟查询

 

4.2 开启延迟加载功能

当我们在进行表关联的时候,有可能在查询结果的时候不需要关联对象的属性值,那么此时可以通过延迟加载来实现功能。在全局配置文件中添加如下属性:

mybatis-config.xml

<settings>
    <!-- 开启延迟加载主要是前两个配置-->
    <!-- 延迟加载,所有分步查询都是懒加载,默认是false(立即加载)-->
    <setting name="lazyLoadingEnabled" value="true" />
    <!-- 当为true时,所有延迟属性都会立即加载;为false时,调用这些延迟属性时才会查询和加载-->
    <setting name="aggressiveLazyLoading" value="false" />
    <!--设置对象的哪些方法调用会加载延迟查询   默认:equals,clone,hashCode,toString-->
    <!-- 最好配置为空,这样可以最大限度延迟加载-->
    <!-- 当前配置的意思为当调用getId时延迟加载-->
    <!-- 可以不配置-->
    <setting name="lazyLoadTriggerMethods" value="getId"/>
    <!-- <setting name="lazyLoadTriggerMethods" value=""/> -->
</settings>

 

设置名

描述

有效值

默认值

lazyLoadingEnabled

延迟加载的全局开关,当开启时所有关联对象都会延迟加载,特定关联关系可以通过设置fetchType属性来覆盖该项的开关状态。

true|false

false

aggressiveLazyLoading

开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。

true|false

false(在3.4.1及之前的版本默认值为true)

lazyLoadTriggerMethods

指定对象的哪些方法触发一次延迟加载。

用逗号分隔的方法列表。

equals,clone,hashCode,toString

 

 

如果设置了全局加载,但是希望在某一个sql语句查询的时候不使用延时策略,可以添加fetchType下属性:

 <association property="dept" fetchType="eager"  column="dept_id"  select="cn.tulingxueyuan.mapper.DeptMapper.SelectDept">
</association>

 

 

4.3 延迟加载的实现

  1. 在resultMap中使用association或者collection,即可使用延迟加载。
  2. 延迟加载需要多个select语句,不能是一条多表连接的select语句。

实现班级学生案例:

1、创建实体类 班级类以及学生类

班级类:

public class Clazz {
    private int id;
    private String name;
    private List<Student> students;
    @Override
    public String toString() {
        return "Clazz{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", students=" + students +
                '}';
    }
    
    // get和set方法省略...
}

学生类:

public class Student {
    private int id;
    private String name;
    private String sex;
    private int clazz;
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", clazz=" + clazz +
                '}';
    }
    // get和set方法省略...
}

2、创建StudentMapper和ClazzMapper接口

ClazzMapper接口:

public interface ClazzMapper {
    /**
     * 根据班级编号查询班级
     * @param id
     * @return
     */
    Clazz selectById(int id);
}

StudentMapper接口:

public interface StudentMapper {
    /**
     * 根据学生编号查找学生
     * @param id
     * @return
     */
    Student selectById(int id);
    /**
     * 根据学生的班级号查找学生
     * @param id
     * @return
     */
    List<Student> selectByClazz(int id);
}

3、创建ClazzMapper.xml和StudentMapper.xml

ClazzMapper.xml:

<!-- 根据班级编号查找班级-->
<select id="selectById" resultMap="rm">
    select * from clazz where id=#{id}
</select>

<!-- 配置映射,根据学生班级号查找学生放进班级里-->
<resultMap id="rm" type="clazz" autoMapping="true">
    <id column="id" property="id"></id>
    <!-- 嵌套查询  懒加载就是对这个字段起作用 -->
    <collection property="students" column="id" select="dao.StudentMapper.selectByClazz"></collection>
</resultMap>

StudentMapper.xml:

<!-- 根据学生id查找学生-->
<select id="selectById" resultType="student">
    select * from student where id=#{id}
</select>
<!-- 根据学生班级号查找学生-->
<select id="selectByClazz" resultType="student">
    select * from student where clazz=#{id}
</select>

4、测试

public class Test {
    public static void main(String[] args) {
        Clazz clazz = new ClazzService().selectById(1); // 1
        System.out.println(clazz.getId()); // 2
        System.out.println(clazz.getName()); // 3
        System.out.println(clazz.getStudents()); // 4
    }
}

当注释掉第4句时,因为没有要求查找学生,按照延迟加载的特性,程序只会查询班级的信息,而不会查询学生的信息,所以只有一条sql语句,就是根据班级编号查找班级的select,并没有查找学生的select:

当放开第四句时,因为clazz.getStudents()需要student数据,所以会执行第二条sql语句将student的信息查询到:

五、总结

三种关联关系都有两种关联查询的方式:1、嵌套查询    2、嵌套结果

Mybatis的延迟加载配置:

在全局配置文件中加入下面代码

<settings>
    <setting name=”lazyLoadingEnabled” value=”true” />
    <setting name=”aggressiveLazyLoading” value=”false”/>
</settings>

在映射文件中,元素和元素中都已默认配置了延迟加载属性,即默认属性fetchType=”lazy”(属性fetchType=”eager”表示立即加载),所以在配置文件中开启延迟加载后,无需在映射文件中再做配置。

 

 

5.1 一对一

使用元素进行一对一关联映射非常简单,只需要参考如下两种示例配置即可

 

5.2 一对多

<resultMap>元素中,包含了一个子元素,MyBatis就是通过该元素来处理一对多关联关系的:

  • <collection>子元素的属性大部分与<association>元素相同,但其还包含一个特殊属性——ofType
    • ofType属性与javaType属性对应,它用于指定实体对象中集合类属性所包含的元素类型。
  • <collection>元素的使用也非常简单,同样可以参考如下两种示例进行配置,具体代码如下:

 

 

5.3 多对多

多对多的关联关系查询,同样可以使用前面介绍的元素进行处理(其用法和一对多关联关系查询语句用法基本相同)

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- cepb.cn 版权所有 湘ICP备2022005869号-7

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务