前言
顾名思义,延迟加载就是把加载的流程后置,从而提高系统的稳定性和可用性的一种手段。在前几篇关于MyBatis
的博文中,我们详细阐述了MyBatis的使用方式以及原理。本博文主要针对一对多,多对多的场景下的延迟加载做一个详细描述。
MyBatis延迟加载
什么是延迟加载
在开发过程中很多时候我们并不需要总是在加载用户信息时就一定要加载他的订单信息。此时就是我们所说的延迟加载。就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
优点
先从单表查询,需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
缺点
因为只有当需要用到数据时,才会进行数据库查询,这样在大批量数据查询时,因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降。
使用场景
- 一对多,多对多:通常情况下采用延迟加载。
- 一对一(多对一):通常情况下采用立即加载。
实现延迟加载
局部延迟加载
在association
和collection
标签中都有一个fetchType
属性,通过修改它的值,可以修改局部的加载策略。
<!-- 开启一对多 延迟加载 -->
<resultMap id="userMap" type="user">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<result column="birthday" property="birthday"></result>
<!-- fetchType="lazy" 懒加载策略 fetchType="eager" 立即加载策略 -->
<collection property="orderList" ofType="order" column="id"
select="com.rubin.dao.OrderMapper.findByUid" fetchType="lazy">
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
SELECT * FROM `user`
</select>
全局延迟加载
在MyBatis
的核心配置文件中可以使用setting
标签修改全局的加载策略。
<settings>
<!--开启全局延迟加载功能-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
延迟加载的原理
它的原理是,使用CGLIB
或Javassist
( 默认 ) 创建目标对象的代理对象。当调用代理对象的延迟加载属性的get
方法时,进入拦截器方法。比如调用 a.getB().getName()
方法,进入拦截器的invoke(...)
方法,发现 a.getB()
需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对象的 SQL
,把 B 查询上来,然后调用 a.setB(b)
方法,于是 a 对象 b 属性就有值了,接着完成a.getB().getName()
方法的调用。这就是延迟加载的基本原理。
写在最后
由于源码比较多,本博文不在讲解源码实现过程。有兴趣的小伙伴可以自行查阅一下源码。这里给出一个源码实现延迟加载步骤,以方便大家阅读源码的时候有个方向:
- 查看解析
XML
的逻辑,会将全局的延迟加载配置封装进Configuration
对象中,把局部延迟加载的配置封装进MappedStatement
中。 - 延迟加载发生在解析查询结果的过程,需要关注
ResultSetHandler
的handleResultSets()
方法,此方法中有关于延迟加载的实现逻辑。
文章评论