JavaLearn#(28)MyBatis高级:无级联查询级联查询(立即加载结果映射延迟加载)多表连接查询MyBatis注解MyBatis运行原理面试题

Posted LRcoding

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaLearn#(28)MyBatis高级:无级联查询级联查询(立即加载结果映射延迟加载)多表连接查询MyBatis注解MyBatis运行原理面试题相关的知识,希望对你有一定的参考价值。

1. 多表查询

前面的操作(MyBatis进阶),不管是用 SqlSession还是 Mapper代理,都是对单个数据库表的操作

在实际开发中,经常会将来自多张表的数据在一个位置显示,比如查询并显示员工信息时,需要展示部门表的部门名称,岗位表的岗位名称等。这就要求 Employee中,要包含部门 Dept岗位 Position的信息

MyBatis是如何实现对多表的查询并组装数据呢?(此时没有DML的事)

  • 方式1:无级联查询——开发者手动完成多表数据的组装,需要执行多条 SQL语句
  • 方式2:级联查询——使用MyBatis映射配置自动完成数据的组装,需要执行多条 SQL语句
  • 方式3:连接查询——使用MyBatis映射配置自动完成数据的组装,只执行一条 SQL语句

以 部门Dept 和 员工Emp 的一对多关系为例:

  • 功能1:查询所有员工的信息(多对一:多个员工对应一个部门)

    包含字段:empno、ename、sal、comm、deptno、dname

    其中 deptno是关联字段,dname来自部门表Dept

  • 功能2:查询10号部门及其该部门员工信息(一对多:一个部门对应多个员工)

    包含字段:deptno、dname、loc。 员工显示empno、ename、sal、comm

1.1 环境搭建

需要定义两个实体类、两个映射接口、两个映射XML文件(注意在mybatis-cfg.xml中告诉映射文件的位置,可以使用package)

需要在两个实体类之间建立关联关系,数据库之间通过外键建立关联,之间通过属性建立关联(使用组合)

  • 一个员工只有一个部门,所以在 Employee中,定义一个 Dept类的成员变量

    public class Employee 
    	private Dept dept;  // 虽然 Dept里包含了 deptno,但是在 Employee中的也不能省略,供 insert和 update使用
        
        // 其他属性省略,均有 getter/setter方法
    
    
  • 一个部门有多个员工,所以在 Dept中,定义一个泛型为 Employee的集合的成员变量

    public class Dept 
    	private List<Employee> employeeList;
        
        // 其他属性省略,均有 getter/setter方法
    
    

1.2 无级联查询(不使用,了解)

需要开发者手动的进行多次查询并完成多表数据的组装,需要执行多个 SQL语句,很少使用

实现功能1(多对一):

  • 测试类中:

    @Test
    public void testFindAll() 
        SqlSession sqlSession = DBUtil.getSqlSession();
    
        EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
    
        List<Employee> employeeList = empMapper.findAll();
        
        // 在代码中进行组装!!!!!!!!!!!!
        for (Employee employee : employeeList) 
            // 获取员工的部门编号
            int deptno = employee.getDeptno();
    
            // 按照部门编号查找部门数据
            Dept dept = deptMapper.findById(deptno);
    
            // 将数据赋给Employee的dept字段
            employee.setDept(dept);
        
        
        DBUtil.closeSqlSession(sqlSession);
        
        employeeList.forEach(emp -> 
            if (emp.getDept() == null) 
                System.out.println(emp.getEmpno() + "\\t" + emp.getEname() + "\\t" + emp.getSal()
                                   + "\\t 无部门");
             else 
                System.out.println(emp.getEmpno() + "\\t" + emp.getEname() + "\\t" + emp.getSal()
                                   + "\\t" + emp.getDept().getDname() + "\\t" + emp.getDept().getLoc());
            
        );
    
    
  • 映射文件

    <!-- EmpMapper.xml中 -->
    <mapper namespace="com.lwclick.mapper.EmpMapper">
        <sql id="empCols">
            empno, ename, sal, hireDate, deptno
        </sql>
    
        <select id="findAll" resultType="employee">
            SELECT <include refid="empCols" /> FROM emp
        </select>
    </mapper>
    
    <!-- DeptMapper.xml中 -->
    <mapper namespace="com.lwclick.mapper.DeptMapper">
        <select id="findById" resultType="dept">
            SELECT * FROM dept WHERE deptno = #param1
        </select>
    </mapper>
    

实现功能2(一对多):

  • 测试类

    @Test
    public void testFindAll() 
        SqlSession sqlSession = DBUtil.getSqlSession();
    
        EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
    
        // 此处应查询所有部门数据,通过循环,获取每个部门的编码
        int deptno = 10;
        Dept dept = deptMapper.findById(deptno);
    
        // 通过部门的编码,查询该部门下所有的员工
        List<Employee> empList = empMapper.findByDeptno(dept.getDeptno());
    
        // 赋给该部门的员工list
        dept.setEmployeeList(empList);
    
        DBUtil.closeSqlSession(sqlSession);
    
        System.out.println(dept.getDeptno() + "\\t" + dept.getDname() + "\\t" + dept.getLoc());
        for (Employee employee : dept.getEmployeeList()) 
            System.out.println("\\t" + employee);
        
    
    
  • 映射文件

    <!-- EmpMapper.xml中 -->
    <select id="findByDeptno" resultType="employee">
        SELECT <include refid="empCols" /> FROM emp
        WHERE deptno = #param1
    </select>
    

1.3 级联查询

级联查询:利用数据库表间的外键关联关系,进行自动的级联查询操作

使用MyBatis实现级联查询,除了要在 实体类中增加关联属性外,还需要在映射文件中进行配置

1.3.1 立即加载

实现功能1(多对一):

  • 测试类

    @Test
    public void testFindAll() 
        SqlSession sqlSession = DBUtil.getSqlSession();
    
        EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
    
        // 无需组装!!!!!!
        List<Employee> employeeList = empMapper.findAll();
    
        DBUtil.closeSqlSession(sqlSession);
    
        employeeList.forEach(emp -> 
            if (emp.getDept() == null) 
                System.out.println(emp.getEmpno() + "\\t" + emp.getEname() + "\\t" + emp.getSal()
                                   + "\\t 无部门");
             else 
                System.out.println(emp.getEmpno() + "\\t" + emp.getEname() + "\\t" + emp.getSal()
                                   + "\\t" + emp.getDept().getDname() + "\\t" + emp.getDept().getLoc());
            
        );
    
    
  • 映射文件(多对一),使用 resultMap,结合 association

    <select id="findAll" resultMap="empResultType">    <!-- 返回类型改为 resultMap -->
        SELECT <include refid="empCols" /> FROM emp
    </select>
    
    <!-- 配置 resultMap -->
    <resultMap id="empResultType" type="employee">
        <!--     
            ================ 如果属性名称,除大小写外都一致,无需手动映射 =============
            // 映射主键 
            <id property="empno" column="empno" />
    
            // 映射非主键列    property:实体类的属性名    column:数据库表的列名
            <result property="ename" column="ename" />
            <result property="job" column="job" />
            <result property="hireDate" column="hireDate" />
            <result property="sal" column="sal" />
    	-->
        
        <!-- 下面也用到了 deptno,所以此处要指定一下 -->
        <result property="deptno" column="deptno" />
    
        <!-- 配置【多对一、一对一】的关联映射 -->         <!-- column:是当前表emp的deptno -->
        <association property="dept" column="deptno"
                     select="com.lwclick.mapper.DeptMapper.findById" />   <!-- select:执行哪个文件下的什么SQL -->
    </resultMap>
    

实现功能2(一对多):

  • 测试类

    @Test
    public void testFindAll() 
        SqlSession sqlSession = DBUtil.getSqlSession();
    
        DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class);
    
        // 不再手动组装
        int deptno = 10;
        Dept dept = deptMapper.findById(deptno);
    
        DBUtil.closeSqlSession(sqlSession);
    
        System.out.println(dept.getDeptno() + "\\t" + dept.getDname() + "\\t" + dept.getLoc());
        for (Employee employee : dept.getEmployeeList()) 
            System.out.println("\\t" + employee);
        
    
    
  • 映射文件(一对多),使用 resultMap,结合 collection

    <select id="findById" resultMap="deptResultMap">    <!-- 返回类型改为 resultMap -->
        SELECT * FROM dept WHERE deptno = #param1
    </select>
    
    <resultMap id="deptResultMap" type="dept">
        <!-- 由于两个表中都有 deptno,所以此处要指定一下 -->
        <id property="deptno" column="deptno" />
        <!-- 配置【一对多】的关联映射 -->       <!-- column是当前表 dept的deptno -->      <!-- ofType:集合中元素的数据类型 -->
        <collection property="employeeList" column="deptno" 
                    ofType="employee" select="com.lwclick.mapper.EmpMapper.findByDeptno" />
    </resultMap>
    

1.3.2 理解 resultMap

在单表情况下,数据库字段名和实体类属性如果一致(不考虑大小写),还可以自动映射Auto-Mapping

在复杂的情况下,就需要对查询结果进行手动映射

<resultMap id=""  type="">
    <!-- 【主键】映射 -->
    <id property="" javaType="" column="" jdbcType="" typeHandler=""></id>
    
    <!-- 【非主键】映射 -->
    <result property="" column=""></result>
    
    <!-- 【多对一、一对一】映射 -->
    <association property="" column="" fetchType="" select=""></association>
    
    <!-- 【一对多】映射 -->
    <collection property="" column="" fetchType="" select="" ofType=""></collection>
</resultMap>

resultMap 元素的属性

  • id:resultMap 的唯一标识
  • type:结果最终形成哪个类的对象,和 resultType一样

resultMap 子元素

  • id:配置主键数据库列对象属性的关系
    • property:需要映射到 JavaBean 的属性名称
    • column:数据表的列名或者列别名
  • result:配置非主键列和属性
    • property、column
  • association:配置多对一、一对一关联字段列和属性,对应一个对象属性
    • property、column
    • fetchType:自动延迟加载
    • select:使用哪个查询获取属性的值,要求指定namespace+id的全名称
  • collection:配置一对多、多对多关联字段和属性,对应一个集合属性
    • property、column、fetchType、select
    • ofType:指明集合中元素的类型

注意:在resultMap手动映射中,一个关联列可能对应多个property,都要进行手动映射(比如 deptno字段)。其他列名和属性名一致的,都可以省略

1.3.3 延迟加载

多表查询中,如果第一个查询结果有 N条记录,随后根据这 N条记录的值,再去查询数据库 N次,共计N+1次查询,对服务器压力很大

  • 解决方法1:延迟加载。关联表的数据,只有等到真正使用的时候才查询,不用不查询
  • 解决方法2:连接查询。一条SQL语句,查询到所有的数据

延迟加载:设置为延迟加载的内容,等到真正使用时才进行查询(比如查询所有员工信息时,只需要显示员工信息,而不显示部门信息,那么部门信息就可以设置默认不查询,等真正要用的时候,再进行查询,提高了数据库的新能)。多用在关联对象和集合中

延迟加载的设置

  • 全局开关:在 mybatis-cfg.xml中打开延迟加载的开关(打开后,所有的 association和 collection都生效

    <settings>
        <!-- 配置延迟加载的开关 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    
    • lazyLoadingEnabled(默认true):是否开启延迟加载。开启后所有关联对象都会延迟加载, fetchType属性会覆盖该项的开关
    • aggressiveLazyLoading(默认true):开启时,任何方法的调用都会加载懒加载对象的所有属性。否则,每个属性按需加载
  • 分开关:指定的某个 association和 collection元素中,配置 fetchType,取值 eager | lazy ,会覆盖全局延迟设置

    <resultMap id="deptResultMap" type="dept">
        <id property="deptno" column="deptno"/>
        <!-- fetchType,配置是否开启延迟加载 -->
        <collection property="employeeList" column="deptno" fetchType="lazy" 
                    select="com.lwclick.mapper.EmpMapper.findByDeptno" />
    </resultMap>
    
  • eager( association 默认 ):立即加载

  • lazy( collection 默认 ):延迟加载

功能1(fetchType=“eager”):

功能1(fetchType=“lazy”):