首页 > Java > Java面试题 > 正文

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

煙雲
发布: 2025-10-22 08:12:01
原创
736人浏览过
MyBatis支持延迟加载,通过配置lazyLoadingEnabled=true和fetchType="lazy"实现按需加载,利用动态代理在访问关联属性时触发SQL查询,提升性能但需注意N+1查询、SqlSession生命周期和序列化问题。

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

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

解决方案

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

<!-- mybatis-config.xml -->
<configuration>
  <settings>
    <!-- 启用延迟加载,默认是true -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!-- 决定延迟加载的粒度:false 表示按需加载单个属性,true 表示访问任何属性时加载所有属性 -->
    <setting name="aggressiveLazyLoading" value="false"/>
  </settings>
  <!-- ... 其他配置 -->
</configuration>
登录后复制

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

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

<!-- OrderMapper.xml -->
<resultMap id="orderResultMap" type="Order">
  <id property="id" column="order_id"/>
  <result property="orderNo" column="order_no"/>
  <!-- 关联用户,配置为延迟加载 -->
  <association property="user" javaType="User" select="com.example.mapper.UserMapper.selectUserById" column="user_id" fetchType="lazy"/>
  <!-- 关联订单项,配置为延迟加载 -->
  <collection property="items" ofType="OrderItem" select="com.example.mapper.OrderItemMapper.selectOrderItemsByOrderId" column="order_id" fetchType="lazy"/>
</resultMap>
登录后复制

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

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

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

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

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

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

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

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

<configuration>
  <settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
  </settings>
</configuration>
登录后复制

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

度加剪辑
度加剪辑

度加剪辑(原度咔剪辑),百度旗下AI创作工具

度加剪辑63
查看详情 度加剪辑

Mapper XML 配置 (<association><collection>):

<resultMap id="orderResultMap" type="Order">
  <!-- ... 其他属性 -->
  <association property="user" javaType="User" select="com.example.mapper.UserMapper.selectUserById" column="user_id" fetchType="lazy"/>
  <collection property="items" ofType="OrderItem" select="com.example.mapper.OrderItemMapper.selectOrderItemsByOrderId" column="order_id" fetchType="lazy"/>
</resultMap>
登录后复制

这里的 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<OrderItem> 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(这通常很复杂)。
  • 理解成本: 虽然用起来透明,但理解其背后的代理机制和生命周期管理,对于排查问题和优化性能至关重要。

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

以上就是mybatis 是否支持延迟加载?延迟加载的原理是什么?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

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