哈喽大家好,我是你们熟悉的、31岁依旧元气满满的程序员大哥哥——小米

作为一个从小写Hello World长大的老Javaer,最近在带团队招人,发现一个现象特别有意思:不少候选人都能回答“MyBatis 支持延迟加载”,但一问“它是怎么实现的?”立刻陷入沉思.jpg

所以今天我决定写一篇彻底掰开揉碎的技术文,用我一贯爱讲故事的方式,和大家一起搞清楚:

  • MyBatis 到底支不支持延迟加载?
  • 它背后的实现原理是什么?
  • 面试官究竟想从这个问题看出你什么水平?
  • 代码里应该怎么用,才能用得又优雅又高效?

延迟加载?你说的是那部老电视剧吗?

说起延迟加载,大家最熟的例子估计是——对象里面嵌套了一个 List,当我查主对象的时候,这个 List 是不是一开始就查出来了?

还记得那天面试一个小哥,简历写着“熟悉MyBatis源码”。我一高兴就来个深水区问题:

“MyBatis 支持延迟加载吗?你平时项目中用过没有?”

小哥眼神亮了一下:“支持!我们之前一个一对多的查询就是懒加载的。”

我追问:“那它是怎么做到的?是不是用了什么动态代理或者别的机制?”

他沉默了三秒,说:“呃……我没看过源码,我猜是懒得加载……”

我当场把“我猜是懒得加载”记进了2025年度最搞笑技术语录 TOP10。

MyBatis 是怎么支持延迟加载的?

好了,回归正题,我们分几个层次搞清楚这事儿。

1. 什么是延迟加载(懒加载)?

用一句话说清楚:

延迟加载(Lazy Loading)指的是:在真正需要用到某个数据的时候,才从数据库里加载它。

举个例子你就懂:

我们有两个表:

被问 MyBatis 懒加载原理,我当场沉默了三秒……_延迟加载

现在你写了个 Java Bean:

被问 MyBatis 懒加载原理,我当场沉默了三秒……_懒加载_02

如果你查 User 的时候,马上把 Address 查出来,那就是立即加载

如果你查 User 的时候,等到调用 user.getAddress() 的时候才查 Address,那就是延迟加载

2. MyBatis 默认支持延迟加载吗?

答案是:支持,但不是默认启用!

你需要在 mybatis-config.xml 里手动打开这个开关:

被问 MyBatis 懒加载原理,我当场沉默了三秒……_延迟加载_03

解释一下这两个配置:

被问 MyBatis 懒加载原理,我当场沉默了三秒……_懒加载_04

所以要延迟加载,这两个配置都要设置对。

3. 哪些场景下支持懒加载?

常见的两种:

  • 一对一(association)
  • 一对多(collection)

例子如下:

被问 MyBatis 懒加载原理,我当场沉默了三秒……_加载_05

这里的 association 就是表示“用户拥有一个地址”,我们可以指定 fetchType="lazy" 表示延迟加载。

底层是怎么实现的?核心逻辑揭秘

到了重点中的重点啦,小米我带你深入源码,搞清楚 MyBatis 到底是怎么玩这个“等你访问我再去查库”的魔法的。

1. 核心类:CglibProxyFactory + JavassistProxyFactory

MyBatis 使用了Java 动态代理技术,让你的对象变成了一个代理对象

当你调用某个方法(比如 getAddress())时,代理会判断:

哟,你终于来了!这个字段还没加载,我这就去数据库里捞!

这个机制是通过 ObjectFactory 和代理类实现的。

MyBatis 的默认实现用了 CGLIB 或 Javassist,帮你生成对象的代理类,在 getter 被调用时触发数据库操作。

2. 延迟加载队列:ResultLoaderMap

在你查主对象的时候,MyBatis 会记录一个延迟加载的任务列表:

被问 MyBatis 懒加载原理,我当场沉默了三秒……_加载_06

里面记录了“要加载哪个字段”、“该字段怎么加载”、“调用哪个SQL语句”。

等你访问属性时,MyBatis 检查 ResultLoaderMap:

被问 MyBatis 懒加载原理,我当场沉默了三秒……_延迟加载_07

3. 看源码核心片段

MyBatis 懒加载的关键代码在:

被问 MyBatis 懒加载原理,我当场沉默了三秒……_延迟加载_08

这段代码就像你租了一套房子,房东说:

“你啥时候想用厨房,我就给你装上灶台。”

懒得很贴心,是吧!

4. 与 Spring 整合时的注意事项

很多小伙伴用的是 Spring + MyBatis,还整合了事务管理。

注意:懒加载必须在 SqlSession 没有关闭之前完成!

否则你访问懒加载字段的时候,就会抛出这个经典异常:

被问 MyBatis 懒加载原理,我当场沉默了三秒……_延迟加载_09

解决方案有几个:

  • 在 service 方法中尽早访问懒加载属性(别等方法返回了才访问)
  • 或者使用 OpenSessionInView 模式
  • 或者考虑是否需要懒加载,改成立即加载更安全

为什么面试官爱问这个问题?

面试官不是真的想听你背配置,而是想看你有没有深入理解 ORM 框架的运行机制。

具体来说,这个问题可以考察你以下几个点:

被问 MyBatis 懒加载原理,我当场沉默了三秒……_加载_10

特别是你应聘中高级岗位,这种“框架原理+实战经验”的题目几乎是标配。

实战建议:用懒加载,你得注意这些坑

小米最后也分享一下我自己在项目中踩过的几个坑,希望大家避坑不走弯路:

1. 不要过度依赖懒加载

懒加载虽然好用,但如果你用了懒加载字段,又在一个循环里调用,就会变成N+1 查询问题

被问 MyBatis 懒加载原理,我当场沉默了三秒……_延迟加载_11

正确方式是用 JOIN 提前查出所有字段,或者写成 batch 查询。

2. 确保懒加载发生在 SqlSession 没关闭的时候

很多时候你调用 getAddress() 的地方是在 Controller 层,结果事务早结束,SqlSession 已关闭……

建议:如果确定要用懒加载,就在 service 层访问一下属性,让懒加载提前触发

3. 多线程 + 懒加载 = 地狱模式

因为代理对象并不是线程安全的,所以在多线程环境下千万别对懒加载字段操作!

否则你得到的对象可能是未初始化、或者正在加载过程中的代理对象。

总结一波,面试高分这样答!

最后我们总结一下,假如你现在是候选人,面对“MyBatis 支持延迟加载吗”这个问题,你可以这么回答:

标准回答(适合面试)

“支持,MyBatis 提供了懒加载功能,适用于一对一和一对多的关联查询。它通过动态代理机制,在访问属性时触发延迟加载操作。实现方式是在对象创建时记录 ResultLoaderMap,当调用 getter 时执行查询,通常配合 <association> 或 <collection> 使用 fetchType='lazy' 来控制是否懒加载。在项目中使用时要注意 SqlSession 的生命周期,避免懒加载失败或性能问题。”

说完这一段,面试官基本就知道你是个“看过源码且下过功夫”的人了。

END

好了,今天这篇干货满满的文章就到这里啦!

我是爱技术也爱写故事的小米,如果你也在学习 MyBatis、正在准备面试、或者单纯想看技术文中的段子,记得点个“在看”+“转发”支持一下哈~

留言区见啦~