1 前言

懒加载针对什么使用的?为什么要用懒加载?
懒加载针对级联使用的,懒加载的目的是减少内存的浪费和减轻系统负担。

懒加载是什么?
你可以理解为按需加载,当我调用到关联的数据时才与数据库交互否则不交互。

N+1 问题
N+1 问题主要是针对分步查询,分步查询就是使用 association 或 collection 标签中的 select 属性来执行另外一个 SQL 映射语句来返回预期的复杂类型。

<association property="department" column="department_id" javaType="department" select="com.thr.mapper.DepartmentMapper.selectDeptByDeptId"/>

在前面的代码中(分步查询),我们所有的级联都已经成功了,但是这样会引发性能问题,就是我们查询数据时,级联的数据也会跟着全部查询出来。但是如果我们暂时只需要部门的信息,而不需要级联对象中的信息,这就会使数据库多执行几条毫无意义的 SQL,导致数据库资源的损耗和系统性能的下降。而如果有多重级联的话则会更加明显,假如现在有 N 个级联,本来我们只要查询主数据,只要一次查询就可以了,但是由于级联的关系,其级联的数据也会跟着查询出来,后面的 SQL 语句还会执行 N 次,这样就造成了 N+1 问题。

2 什么是延迟加载

延迟加载也叫做懒加载、惰性加载。需要的数据直接取出,不需要的数据待使用时再加载。在 mybatis 中,resultMap 可以实现高级映射中 association、collection 具备延迟加载功能。

3 如何开启延迟加载

开启延迟加载有两种方式:

全局配置延迟加载:这种方式就是给所有的级联配置延迟加载。Mybatis 默认是不开启延迟加载的,需要我们去全局配置文件中打开延迟加载。

  • lazyLoadingEnabled。全局延迟加载开关,开启时,所有级联对象都延迟加载。默认为 false。
  • aggressiveLazyLoading。启用时,有延迟加载属性的对象在被调用时加载该对象的所有属性;否则按需加载。flase,3.4.1 前默认 true.
<settings>
    <!-- 开启延迟加载,不配置默认关闭该特性-->
    <setting name="lazyLoadingEnabled" value="true"></setting>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

fetchType 属性为局部配置延迟加载:fetchType 出现在级联元素 association 和 collection 中,它存在两个值:

  • eager:立即加载对应的数据。
  • lazy:延迟加载对应的数据。
<resultMap id="empAndDeptByStepResultMap" type="Emp">
	<id property="eid" column="eid"></id>
	<result property="empName" column="emp_name"></result>
	<result property="age" column="age"></result>
	<result property="sex" column="sex"></result>
	<result property="email" column="email"></result>
	<association property="dept"
				 select="com.atguigu.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
				 column="did"
				 fetchType="lazy"></association>
</resultMap>

4 全局延迟加载示例

创建员工类 & 部门类 & 员工 Mapper 接口 & 部门 Mapper 接口

public class Department {
    private Integer deptId;
    private String deptName;
    private List<Employee> employees;
}
 
public interface DepartmentMapper {
    List<Department> selectAll();
    Department selectDeptByDeptId(@Param("id") Integer deptId);
}

创建部门 SQL 映射文件

<mapper namespace="com.thr.mapper.DepartmentMapper">
    <resultMap id="departmentMap" type="com.thr.pojo.Department">
        <id property="deptId" column="department_id"/>
        <result property="deptName" column="department_name"/>
        <collection property="employees" ofType="employee" column="department_id"
                    select="com.thr.mapper.EmployeeMapper.selectEmpByDeptId">
        </collection>
    </resultMap>
 
    <select id="selectAll" resultMap="departmentMap">
      SELECT * FROM t_department
    </select>
 
    <select id="selectDeptByDeptId" parameterType="int" resultMap="departmentMap">
        SELECT * FROM t_department WHERE department_id = #{id}
    </select>
</mapper>

配置 MyBatis 的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 
<configuration>
    <properties resource="db.properties"/>
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    <typeAliases>
        <package name="com.thr.pojo"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${database.driver}"/>
                <property name="url" value="${database.url}"/>
                <property name="username" value="${database.username}"/>
                <property name="password" value="${database.password}"/>
            </dataSource>
        </environment>
    </environments>
 
    <mappers>
        <package name="com.thr.mapper"/>
    </mappers>
</configuration>

配置数据库连接 & 日志文件

db.properties

database.driver=com.mysql.cj.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
database.username=root
database.password=root

log4j.properties

log4j.rootLogger=DEBUG, Console
#Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

测试代码

@Test
public void testSelectAll(){
	List<Department> departments = mapper.selectAll();
	for (Department department : departments) {
		System.out.println(department.getDeptId()+"--"+department.getDeptName());
		System.out.println("========");
		//执行 getEmployees()去查询员工信息,这里实现按需加载
		//System.out.println(department.getEmployees());
	}
}

查看日志输出
MyBatis——懒加载:lazyLoadingEnabled 和 aggressiveLazyLoading 的使用
aggressiveLazyLoading 的属性为 false,即每种属性按需加载,不调用就不加载。

2019-11-26 19:37:21,627 128    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2019-11-26 19:37:22,323 824    [           main] DEBUG source.pooled.PooledDataSource  - Created connection 793138072.
2019-11-26 19:37:22,326 827    [           main] DEBUG  com.itheima.dao.IUser.findAll  - ==>  Preparing: select * from user
2019-11-26 19:37:22,347 848    [           main] DEBUG  com.itheima.dao.IUser.findAll  - ==> Parameters:
2019-11-26 19:37:22,400 901    [           main] DEBUG  com.itheima.dao.IUser.findAll  - <==      Total: 4
-----每个用户的信息------
丽华
-----每个用户的信息------
丽华
-----每个用户的信息------
丽华
-----每个用户的信息------
丽华
2019-11-26 19:37:22,401 902    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [ com.mysql.cj.jdbc.ConnectionImpl@2f465398 ]
2019-11-26 19:37:22,401 902    [           main] DEBUG source.pooled.PooledDataSource  - Returned connection 793138072 to pool.

将 aggressiveLazyLoading 设置为 true:只要对这个类的任意操作将完整加载整个类的所有属性即执行级联的 SQL 语句

2019-11-26 19:35:33,893 123    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Opening JDBC Connection
2019-11-26 19:35:34,597 827    [           main] DEBUG source.pooled.PooledDataSource  - Created connection 793138072.
2019-11-26 19:35:34,600 830    [           main] DEBUG  com.itheima.dao.IUser.findAll  - ==>  Preparing: select * from user
2019-11-26 19:35:34,621 851    [           main] DEBUG  com.itheima.dao.IUser.findAll  - ==> Parameters:
2019-11-26 19:35:34,677 907    [           main] DEBUG  com.itheima.dao.IUser.findAll  - <==      Total: 4
-----每个用户的信息------
2019-11-26 19:35:34,678 908    [           main] DEBUG com.itheima.dao.IRole.findById  - ==>  Preparing: select * from role where user_id=?
2019-11-26 19:35:34,679 909    [           main] DEBUG com.itheima.dao.IRole.findById  - ==> Parameters: 1(String)
2019-11-26 19:35:34,680 910    [           main] DEBUG com.itheima.dao.IRole.findById  - <==      Total: 2
丽华
-----每个用户的信息------
2019-11-26 19:35:34,680 910    [           main] DEBUG com.itheima.dao.IRole.findById  - ==>  Preparing: select * from role where user_id=?
2019-11-26 19:35:34,680 910    [           main] DEBUG com.itheima.dao.IRole.findById  - ==> Parameters: 2(String)
2019-11-26 19:35:34,681 911    [           main] DEBUG com.itheima.dao.IRole.findById  - <==      Total: 2
丽华
-----每个用户的信息------
2019-11-26 19:35:34,681 911    [           main] DEBUG com.itheima.dao.IRole.findById  - ==>  Preparing: select * from role where user_id=?
2019-11-26 19:35:34,681 911    [           main] DEBUG com.itheima.dao.IRole.findById  - ==> Parameters: 4(String)
2019-11-26 19:35:34,682 912    [           main] DEBUG com.itheima.dao.IRole.findById  - <==      Total: 0
丽华
-----每个用户的信息------
2019-11-26 19:35:34,682 912    [           main] DEBUG com.itheima.dao.IRole.findById  - ==>  Preparing: select * from role where user_id=?
2019-11-26 19:35:34,682 912    [           main] DEBUG com.itheima.dao.IRole.findById  - ==> Parameters: 5(String)
2019-11-26 19:35:34,683 913    [           main] DEBUG com.itheima.dao.IRole.findById  - <==      Total: 3
丽华
2019-11-26 19:35:34,684 914    [           main] DEBUG ansaction.jdbc.JdbcTransaction  - Closing JDBC Connection [ com.mysql.cj.jdbc.ConnectionImpl@2f465398 ]
2019-11-26 19:35:34,684 914    [           main] DEBUG source.pooled.PooledDataSource  - Returned connection 793138072 to pool.

5 局部延迟加载示例

fecthLazy 实现局部延迟加载的方式配置非常简单,仅仅更改映射文件中的属性即可。

将我们需要设置为延迟加载的地方设置 fecthLazy=lazy 即可

    <resultMap id="departmentMap" type="com.thr.pojo.Department">
        <id property="deptId" column="department_id"/>
        <result property="deptName" column="department_name"/>
        <collection property="employees" ofType="employee" column="department_id"
                    select="com.thr.mapper.EmployeeMapper.selectEmpByDeptId" fetchType="lazy">
        </collection>
    </resultMap>