在学习 MyBatis 的过程中,延迟加载是一个高频考点,也是优化数据库查询性能的重要技巧。今天就通过两个经典面试题,把延迟加载的概念、使用方式和底层原理一次讲透。
一、MyBatis 是否支持延迟加载?
1. 什么是延迟加载?
延迟加载 (Lazy Loading)指的是:在需要用到数据时才进行加载,不需要用到数据时就不加载数据。
举个例子:查询用户信息时,用户和订单往往是一对多的关系。如果我们只想查看用户的基本资料,就不必立刻把该用户的所有订单查出来,等到真正点开"我的订单"时再去查询。这种按需加载的策略,就是延迟加载。
2. MyBatis 的支持情况
MyBatis 对延迟加载提供了良好的支持,具体体现在:
- 支持 一对一关联对象 的延迟加载(
association) - 支持 一对多关联集合对象 的延迟加载(
collection)
3. 如何开启延迟加载?
在 MyBatis 的核心配置文件中,可以通过 lazyLoadingEnabled 参数来控制全局的延迟加载行为:
<settings>
<!-- 开启延迟加载,默认值为 false -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
当该配置设为 true 后,所有满足条件的关联查询都会启用延迟加载。如果想对某个特定的关联查询做细粒度控制,还可以使用 fetchType 属性,它可以覆盖全局配置。
一对一(association)的局部控制示例:
<resultMap id="orderResultMap" type="Order">
<!-- ...其他映射... -->
<association property="user" column="user_id"
javaType="User" select="findUserById"
fetchType="lazy"/>
</resultMap>
一对多(collection)的局部控制示例:
<resultMap id="userResultMap" type="User">
<!-- ...其他映射... -->
<collection property="orders" column="user_id"
ofType="Order" select="findOrdersByUserId"
fetchType="lazy"/>
</resultMap>
fetchType 可选值:
lazy:延迟加载eager:立即加载
无论全局 lazyLoadingEnabled 是否开启,fetchType 都会优先起作用。比如全局关闭延迟加载,但某个关联属性用 fetchType="lazy" 修饰,那它依然是延迟加载;反之亦然。
二、延迟加载的底层原理
这个问题稍微深入一些,但理解之后能帮你更好地掌握 MyBatis 的工作机制。
MyBatis 的延迟加载,本质上是通过 动态代理 来实现的,默认使用 CGLIB 库。整个过程可以分为三步:
1. 使用 CGLIB 创建主对象的代理对象
当执行完主查询(比如根据 ID 查 User)后,MyBatis 并不会直接返回那个原始的 User 实例,而是为它创建一个 CGLIB 代理对象 。这个代理对象是 主对象(User)本身的代理,而非关联对象(如订单列表)的代理。
代理对象会拦截所有方法,尤其是那些针对延迟加载属性的 getter 方法。
2. 调用目标方法时进入拦截器
当你真正调用代理对象的某个方法(例如 user.getOrders())时,会进入 CGLIB 的 invoke 拦截器。在拦截器内部,MyBatis 会检查关联属性当前的值:
- 如果发现该属性还是
null(或者是一个未加载的占位对象),就说明数据尚未加载。 - 这时,拦截器会触发真实的 SQL 查询,把关联数据从数据库中查出来。
3. 获取数据后通过 setter 方法赋值
SQL 查询执行完毕后,拿到了关联数据(比如订单列表),拦截器会调用目标对象对应的 setter 方法 ,将查到的数据注入到主对象的属性中。
设置完成后,再接着执行最初被拦截的方法,此时关联属性已经有值了,程序就能正常获取到完整的数据。
整个流程用一句话概括:先把空壳对象(代理)给你,等你真正要用关联属性时,再去数据库取回数据并"填"进去。
总结
- 什么是延迟加载:按需加载,不是立即查询所有关联数据。
- MyBatis 支持情况 :支持一对多、一对一的延迟加载,通过全局
lazyLoadingEnabled或局部fetchType配置。 - 底层原理:基于 CGLIB 为主对象创建代理,在访问 getter 方法时触发二次查询,再将结果注入主对象,最终返回已有值的属性。
理解延迟加载不仅能帮你应对面试,更能在实际项目中合理控制 SQL 执行时机,避免不必要的大查询,提升系统性能。