什么是延迟加载?

问题:在开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息。此时就是我 们所说的延迟加载。

举个例子:

*在一对多中,当我们有一个用户,它有个100个订单
在查询用户的时候,要不要把关联的订单查出来? 延时加载
在查询订单的时候,要不要把关联的用户查出来? 立即加载

*回答
在查询用户时,用户下的订单应该是,什么时候用,什么时候查询。
在查询订单时,订单所属的用户信息应该是随着订单一起查询出来。

延迟加载

就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。

* 优点:
先从单表查询,需要时再从关联表去关联查询,大大提升数据库性能,因为查询单表要比关联查询多张表速度要快。

* 缺点:
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。

* 在多表中:
一对多,多对多:通常情况下采用延迟加载
一对一(或多对一):通常情况下采用立即加载

* 注意:
延迟加载是基于嵌套查询来实现的

什么是嵌套查询呢

比如:

  1. --先查询id=1的用户信息
  2. select * from user where id=1
  3. --再得到用户id的情况下,再查询用户id=1的相关联的订单信息
  4. select * from order where uid=1

image.png

发送嵌套查询:
User user = sqlSession.selectOne(“com.lagou.mapper.IUserMapper.findById”, 1);

立即加载得到结果如下:(默认是立即加载的)
上面我添加了一个fetchType就是延迟加载,如果取消fetchType属性就是立即加载。默认是立即加载

image.png

实现步骤

在上面的嵌套查询的基础上,实现延时加载。

局部延迟加载

在association和collection标签中都有一个fetchType属性,通过修改它的值,可以修改局部的加载策略。

  1. <!-- 开启一对多 延迟加载 -->
  2. <resultMap id="userMap" type="com.lagou.pojo.User">
  3. <id property="id" column="id"></id>
  4. <result property="username" column="username"></result>
  5. <!--
  6. fetchType="lazy" 懒加载策略
  7. fetchType="eager" 立即加载策略
  8. -->
  9. <collection property="orderList" ofType="com.lagou.pojo.Order"
  10. select="com.lagou.mapper.IOrderMapper.findOrderByUid" column="id" fetchType="lazy">
  11. <id property="id" column="oid"/>
  12. <result property="orderTime" column="ordertime"/>
  13. <result property="total" column="total"/>
  14. </collection>
  15. </resultMap>
  16. <select id="findAll" resultMap="userMap" >
  17. select u.*,o.id oid,o.ordertime,o.total,o.uid from user u left join orders o on o.uid = u.id
  18. </select>

添加了延时加载,那么第一次查询只查用户信息,只有调用订单信息才会去执行订单sql
image.png

全局延迟加载

在Mybatis的核心配置文件(就是数据库配置文件)中可以使用setting标签修改全局的加载策略。
如果配置全局的,那么局部的那个*fetchType
就必须删除

   <settings>
        <!--开启全局的二级缓存配置
        <setting name="cacheEnabled" value="true"/>-->

        <!--开启全局延迟加载功能-->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>

采用全局+局部

注意: 局部的加载策略是优先级高于全局的加载策略的
如果一对一需要才用立即加载,在不影响全局的延时加载,那么就在具体的一对一哪里配置立即加载
在被嵌套的查询语句里面配置fetchType=”eager”(立即加载)

<!-- 关闭⼀对⼀ 延迟加载 -->
<resultMap id="orderMap" type="order">
   <id column="id" property="id"></id>
   <result column="ordertime" property="ordertime"></result>
   <result column="total" property="total"></result>
     <!--
       fetchType="lazy" 懒加载策略
       fetchType="eager" 立即加载策略
     -->
   <association property="user" column="uid" javaType="user"
         select="com.lagou.dao.UserMapper.findById" fetchType="eager">
   </association>
</resultMap>

<select id="findAll" resultMap="orderMap">
 SELECT * from orders
</select>

延迟加载原理实现

它的原理是(其实就是动态代理),使用 CGLIB 或 Javassist( 默认 ) 的其中一种,创建目标对象的代理对象。当调用代理对象的延迟加载属性的 getting 方法时,进入拦截器方法(因为返回的是代理对象,代理对象调用任意方法都会进入到invoke方法)。
比如调用 user.getOrder().getName() 方法,进入拦截器的 invoke(…) 方法,发现 user.getOrder() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 Order 对象的 SQL ,把 Order 查询上来,然后调用 user.setOrder(order) 方法,于是 user 对象 order 属性就有值了,接着完成 user.getOrder().getName() 方法的调用。这就是延迟加载的基本原理

总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载。

延迟加载原理(源码剖析)

MyBatis延迟加载主要使用:Javassist,Cglib实现,类图展示:

   ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2397310/1626924436427-0c9b6b74-e7e4-4d4a-bbe7-2775191dc711.png#clientId=uf5481347-4caf-4&from=paste&height=213&id=u1a103644&margin=%5Bobject%20Object%5D&name=image.png&originHeight=213&originWidth=522&originalType=binary&ratio=1&size=33239&status=done&style=none&taskId=udd7e2137-933f-41f7-a12f-71df9fa85c2&width=522)<br />  

1、
因为是在核心配置文件(sqlMapconfig.xml)添加了全局懒加载,那么配置文件解析的时候,肯定会对它进行解析然后封装成对应的对象的。
image.png

2、
image.png

Setting 配置加载:

那么除了配置lazyLoadingEnabled之外,还有其他属性是可以配置延时加载的
image.png
image.png
具体详细介绍可看mybatis官网: https://mybatis.org/mybatis-3/zh/configuration.html
配置好这些信息后,当对配置进行加载后,会将解析后的信息封装到全局配置对象中Configuration

延迟加载代理对象创建

Mybatis的查询结果是由ResultSetHandler接口的handleResultSets()方法处理的。ResultSetHandler 接口只有一个实现,DefaultResultSetHandler,接下来看下延迟加载相关的一个核心的方法 createResultObject ()

其实可以从select语句里面去看。

1、
image.png

2、省略其中一部分
直接到从数据库查询(因为只有从数据库查询返回的结果集才需要进行封装)
image.png

3、
image.png

4、
image.png

5、
image.png

6、
image.png

7、
image.png
image.png

它只有一个实现类: DefaultResultSetHandler。到此找到这个核心类。

8、那么接下来查看这个核心方法,了解它的延迟加载是通过何种方式去创建的
image.png

9、
image.png

10、
image.png
image.png
这里就得到了 默认生成的JavassistProxyFactory
image.png

11、
image.png

12、
image.png

13、
image.png

14、
image.png

15、
在上面的第8点,就晓得返回的对象就是一个代理对象,
在第13步是传入一个EnhancedResultObjectProxyImpl类的。
image.png
因为EnhancedResultObjectProxyImpl是MethodHandler的实现类,必然也会实现invoke方法,当14点的代理对象执行器调用的时候,肯定也会去执行invoke方法的。

16、
image.png

17、回顾
image.png
image.png