一、延迟加载是什么?
延迟加载(Lazy Loading)又称“惰性加载”,指的是:
当查询一个对象时,不立即加载它的关联对象(如一对多、多对一关系),而是在第一次真正使用该关联对象时才去执行 SQL 查询加载它。
举个例子:
User user = userMapper.selectById(1);
// 此时只查了 user 表,不查 order 表
user.getOrders(); // 这一步才去执行查询 orders 的 SQL
这样可以避免一次性加载大量无关数据,提高查询性能。
二、如何开启延迟加载?
在 MyBatis 的mybatis-config.xml
中配置:
<settings><!-- 全局启用延迟加载 --><setting name="lazyLoadingEnabled" value="true"/><!-- 设置为 true 时,所有关联对象都会延迟加载 --><setting name="aggressiveLazyLoading" value="false"/>
</settings>
aggressiveLazyLoading:
true
(旧版本默认):访问任意属性时会触发所有懒加载属性;false
:只在访问对应属性时才加载(推荐,性能更好)。
然后在映射文件中配置关联关系:
<resultMap id="userMap" type="User"><id property="id" column="id"/><result property="name" column="name"/><collection property="orders"select="selectOrdersByUserId"column="id"/>
</resultMap>
此时,当访问user.getOrders()
时,才会触发对Order
的查询。
lazyLoadingEnabled
配置是全局开关,也可以在单个映射关系上通过属性fetchType="lazy"
来开启懒加载:
<resultMap id="userMap" type="User"><id property="id" column="id"/><result property="name" column="name"/><collection property="orders"fetchType="lazy"select="selectOrdersByUserId"column="id"/>
</resultMap>
三、实现原理(源码层面)
延迟加载的核心为动态代理机制。
查询阶段:创建代理对象
MyBatis 在查询阶段会扫描每个ResultMap
的映射字段,判断哪些字段需要懒加载,然后为结果对象创建代理。
在DefaultResultSetHandler.handleResultSets()
里,MyBatis 读取 ResultSet 时会调用getRowValue()
:
![]() |
这个方法会为每一行创建 Java 对象(例如User
),并填充属性。注意其中的createResultObject
方法和applyPropertyMappings
方法,createResultObject
创建了结果对象的动态代理对象:
![]() |
![]() |
![]() |
注意createResultObject
方法接收lazyLoader
作为参数,此时lazyLoader
内部是个空集合。
下一步applyPropertyMappings
方法才在lazyLoader
中添加用于加载不同属性(可能有多个关联属性)的ResultLoader
:
![]() |
![]() |
![]() |
这样返回的user
实际上是一个代理对象,它的某些属性还没被真正赋值。
访问阶段:触发加载
当我们在 Java 代码中第一次调用:
user.getOrders();
其方法被代理,代理逻辑封装在EnhancedResultObjectProxyImpl
,其intercept
方法中,使用ResultLoaderMap lazyLoader
加载了 Getter 方法所获取的属性:
![]() |
ResultLoaderMap#load
最终通过ResultLoader#loadResult
加载属性值:
![]() |
![]() |
![]() |
ResultLoader#loadResult
通过Executor
查询到结果:
![]() |
这样,数据就在真正访问时被加载。