面试题:MyBatis延迟加载的底层原理

在学习 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 执行时机,避免不必要的大查询,提升系统性能。

相关推荐
敖正炀3 小时前
Spring Boot + MyBatis 企业级数据访问层实战:从选型到分库分表的深度演进
mybatis
敖正炀5 小时前
多数据源与读写分离中间件
mybatis
胡楚昊6 小时前
BUU WEB之旅(1)
java·数据库·mybatis
敖正炀7 小时前
MyBatis 通用插件库与性能监控平台
mybatis
敖正炀8 小时前
手写简易 MyBatis 框架(mini-mybatis)—— 完善版架构设计与核心实现
后端·mybatis
敖正炀8 小时前
反模式与排查宝典:MyBatis 常见陷阱与排错指南
mybatis
_Evan_Yao9 小时前
return 的迷途:try-catch-finally 中 return 的诡异顺序与 Spring 事务暗坑
java·后端·spring·mybatis
Java成神之路-1 天前
MyBatis工作原理
mybatis
敖正炀2 天前
MyBatis 性能调优:批处理、流式查询与 SQL 优化
mybatis