0

0

mybatis 是否支持延迟加载?延迟加载的原理是什么?

煙雲

煙雲

发布时间:2025-10-22 08:12:01

|

770人浏览过

|

来源于php中文网

原创

MyBatis支持延迟加载,通过配置lazyLoadingEnabled=true和fetchType="lazy"实现按需加载,利用动态代理在访问关联属性时触发SQL查询,提升性能但需注意N+1查询、SqlSession生命周期和序列化问题。

mybatis 是否支持延迟加载?延迟加载的原理是什么?

MyBatis 确实支持延迟加载(Lazy Loading),而且这功能在实际项目里简直是性能优化的利器。说白了,它的核心思想就是“按需加载”——只有当你真正需要某个关联数据的时候,MyBatis 才会去数据库里把它捞出来,而不是一股脑地全部加载进来。这对于处理复杂对象图和大数据量关联查询时,能显著减少内存占用和数据库交互次数,让你的应用响应更快。

解决方案

要让 MyBatis 玩转延迟加载,主要是在配置和映射文件里做文章。首先,全局配置里得把 lazyLoadingEnabled 这个开关打开,通常它在 MyBatis 3.x 之后默认就是 true 了,但明确设置一下总是没错的。



  
    
    
    
    
  
  

这里 aggressiveLazyLoading 挺有意思的。当它设为 false 时,MyBatis 会尽量做到“极致”的延迟加载,只有当你访问到某个关联对象的具体属性时,才会去加载那个对象。如果设为 true,那么只要你一访问到那个代理对象本身(比如调用它的任何方法),MyBatis 就会把这个对象的所有属性都加载进来。我个人偏向于设为 false,这样才真正体现了延迟加载的精髓。

接着,在你的 Mapper XML 文件里,针对那些你希望延迟加载的 (一对一)或 (一对多)标签,加上 fetchType="lazy" 属性。



  
  
  
  
  
  

这样一来,当你查询一个 Order 对象时,MyBatis 不会立即去查 UserOrderItem 的数据。只有当你代码里真正去调用 order.getUser() 或者 order.getItems() 时,MyBatis 才会默默地发起新的 SQL 查询。

MyBatis延迟加载的工作原理是什么?

聊到原理,MyBatis 的延迟加载玩的是“代理”这套把戏。说白了,当 MyBatis 从数据库里查到一个主对象(比如 Order)时,如果它发现这个对象有配置了延迟加载的关联属性(比如 UserList),它并不会直接把这些关联数据也查出来。相反,它会给这些关联属性生成一个“替身”,也就是一个动态代理对象。

这个代理对象,有点像一个“空壳子”,它实现了原始关联对象的接口或者继承了原始关联对象的类。当你首次尝试访问这个代理对象的任何方法时(比如 order.getUser().getName()),这个代理对象就会“醒过来”。它会拦截你的方法调用,然后触发 MyBatis 去执行之前在 Mapper XML 里配置好的那个 select 语句(比如 selectUserById),真正地从数据库里把关联数据加载进来。数据加载完成后,这个代理对象会把真实的数据填充进去,或者将后续的调用委托给这个真实的对象。

整个过程对开发者来说几乎是透明的,你感觉就像直接操作真实对象一样。这种机制,避免了在不需要关联数据时就进行额外的数据库查询,从而显著提升了初始查询的性能。当然,这一切都离不开 SqlSession 的功劳,它得保持活跃,才能在需要时触发这些后续查询。如果 SqlSession 提前关闭了,那这个代理对象就“失灵”了,再访问它就会出问题。

如何在MyBatis中配置和使用延迟加载?

配置方面,前面其实已经提到了核心点:mybatis-config.xml 里的全局设置和 Mapper XML 里的 fetchType 属性。

全局配置 (mybatis-config.xml):


  
    
    
  

lazyLoadingEnabled 设为 true 是基础,它告诉 MyBatis 启用延迟加载机制。aggressiveLazyLoading 设为 false 是我个人比较推荐的,它让延迟加载更“懒”,只有当你真正访问到关联对象的某个具体属性时,才会去触发加载。如果设为 true,那么只要你一访问到那个代理对象,它就会把所有关联数据都加载进来,这在某些场景下可能就失去了延迟加载的意义。

晓象AI资讯阅读神器
晓象AI资讯阅读神器

晓象-AI时代的资讯阅读神器

下载

Mapper XML 配置 ():


  
  
  

这里的 fetchType="lazy" 是关键。它明确告诉 MyBatis,useritems 这两个属性在加载 Order 对象时,不要立即去数据库查询,而是等它们被访问时再查。select 属性指向的是一个独立的查询语句,MyBatis 会在需要时调用这个语句来获取关联数据,column 属性则提供了关联查询的参数。

使用示例:

在你的 Java 代码中,使用起来和普通对象没什么两样:

// 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
  OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
  Order order = orderMapper.selectOrderById(1L); // 此时 User 和 OrderItem 并未加载

  System.out.println("订单号: " + order.getOrderNo()); // 不会触发关联加载

  // 访问 user 对象,此时会触发 UserMapper.selectUserById 查询
  User user = order.getUser();
  System.out.println("下单用户: " + user.getName());

  // 访问 items 集合,此时会触发 OrderItemMapper.selectOrderItemsByOrderId 查询
  List items = order.getItems();
  for (OrderItem item : items) {
    System.out.println("  商品: " + item.getProductName() + ", 数量: " + item.getQuantity());
  }

} finally {
  sqlSession.close(); // 关闭SqlSession
}

可以看到,代码里你无需感知到延迟加载的存在,直接调用 getUser()getItems() 即可。MyBatis 会在后台帮你处理好一切。

延迟加载有哪些优缺点,以及使用时需要注意什么?

延迟加载这东西,用好了是神器,用不好也可能挖坑。

优点:

  • 性能提升: 这是最直接的。它减少了初始查询的数据量和数据库交互次数。比如你查一个订单列表,但大多数时候并不需要立即知道每个订单的具体用户或商品详情,延迟加载就能让你快速拿到列表,只有当用户点击某个订单查看详情时,才去加载那些关联数据。
  • 内存优化: 减少了一次性加载到内存中的数据量,特别是在处理复杂对象图时,能有效避免内存溢出。
  • 带宽节省: 减少了数据库和应用服务器之间的数据传输量。

缺点与注意事项:

  • N+1 查询问题: 这是延迟加载最常遇到的坑。如果你查询了一个订单列表,然后遍历这个列表,对每个订单都去访问它的延迟加载属性(比如 order.getUser().getName()),那么对于 N 个订单,MyBatis 就会发出 N+1 次查询(1 次查订单列表,N 次查用户)。这会导致大量的数据库往返,性能反而会急剧下降。
    • 解决方案: 对于经常需要一起查询的关联数据,考虑使用 JOIN 语句在一次查询中搞定,或者在 MyBatis 中使用 fetchType="eager"。MyBatis 3.5.2 以后引入的 select="someMapper.someMethod" fetchType="lazy" 配合 resultMapassociationcollection 可以很好地控制,但如果发现 N+1,还是得考虑 JOIN 或批量查询。
  • SqlSession 生命周期: 前面提到了,延迟加载依赖于 SqlSession 的活跃状态。如果你的 SqlSession 在访问延迟加载属性之前就关闭了,那么你就会遇到类似 LazyInitializationException(虽然 MyBatis 不会直接抛这个,但你会拿到一个未加载的代理对象,或者直接报错)的问题。这在 Web 应用中尤其常见,因为请求结束通常会关闭 SqlSession
    • 解决方案: 确保在访问所有延迟加载属性之前,SqlSession 仍然是打开的。在 Spring 这样的框架中,通常通过事务管理器来管理 SqlSession 的生命周期,确保在一个事务(或请求)的整个过程中 SqlSession 都是可用的。
  • 序列化问题: 如果你尝试序列化一个包含延迟加载代理对象的实体,而代理对象内部的真实数据尚未加载,那么在反序列化之后,这个代理对象可能就无法正常工作了,因为它失去了与 SqlSession 的连接。
    • 解决方案: 在序列化之前强制加载所有关联数据,或者在反序列化之后重新关联 SqlSession(这通常很复杂)。
  • 理解成本: 虽然用起来透明,但理解其背后的代理机制和生命周期管理,对于排查问题和优化性能至关重要。

总的来说,延迟加载是一个强大的工具,但它要求你对数据访问模式有清晰的理解。不是所有关联数据都适合延迟加载,关键在于平衡初始加载速度和后续数据访问的效率。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

838

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

741

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

737

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

399

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16926

2023.08.03

PS使用蒙版相关教程
PS使用蒙版相关教程

本专题整合了ps使用蒙版相关教程,阅读专题下面的文章了解更多详细内容。

23

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7.1万人学习

Java 教程
Java 教程

共578课时 | 48万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号