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

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 不会立即去查 User 和 OrderItem 的数据。只有当你代码里真正去调用 order.getUser() 或者 order.getItems() 时,MyBatis 才会默默地发起新的 SQL 查询。
聊到原理,MyBatis 的延迟加载玩的是“代理”这套把戏。说白了,当 MyBatis 从数据库里查到一个主对象(比如 Order)时,如果它发现这个对象有配置了延迟加载的关联属性(比如 User 或 List<OrderItem>),它并不会直接把这些关联数据也查出来。相反,它会给这些关联属性生成一个“替身”,也就是一个动态代理对象。
这个代理对象,有点像一个“空壳子”,它实现了原始关联对象的接口或者继承了原始关联对象的类。当你首次尝试访问这个代理对象的任何方法时(比如 order.getUser().getName()),这个代理对象就会“醒过来”。它会拦截你的方法调用,然后触发 MyBatis 去执行之前在 Mapper XML 里配置好的那个 select 语句(比如 selectUserById),真正地从数据库里把关联数据加载进来。数据加载完成后,这个代理对象会把真实的数据填充进去,或者将后续的调用委托给这个真实的对象。
整个过程对开发者来说几乎是透明的,你感觉就像直接操作真实对象一样。这种机制,避免了在不需要关联数据时就进行额外的数据库查询,从而显著提升了初始查询的性能。当然,这一切都离不开 SqlSession 的功劳,它得保持活跃,才能在需要时触发这些后续查询。如果 SqlSession 提前关闭了,那这个代理对象就“失灵”了,再访问它就会出问题。
配置方面,前面其实已经提到了核心点: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,那么只要你一访问到那个代理对象,它就会把所有关联数据都加载进来,这在某些场景下可能就失去了延迟加载的意义。
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,user 和 items 这两个属性在加载 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 会在后台帮你处理好一切。
延迟加载这东西,用好了是神器,用不好也可能挖坑。
优点:
缺点与注意事项:
order.getUser().getName()),那么对于 N 个订单,MyBatis 就会发出 N+1 次查询(1 次查订单列表,N 次查用户)。这会导致大量的数据库往返,性能反而会急剧下降。JOIN 语句在一次查询中搞定,或者在 MyBatis 中使用 fetchType="eager"。MyBatis 3.5.2 以后引入的 select="someMapper.someMethod" fetchType="lazy" 配合 resultMap 的 association 和 collection 可以很好地控制,但如果发现 N+1,还是得考虑 JOIN 或批量查询。SqlSession 生命周期: 前面提到了,延迟加载依赖于 SqlSession 的活跃状态。如果你的 SqlSession 在访问延迟加载属性之前就关闭了,那么你就会遇到类似 LazyInitializationException(虽然 MyBatis 不会直接抛这个,但你会拿到一个未加载的代理对象,或者直接报错)的问题。这在 Web 应用中尤其常见,因为请求结束通常会关闭 SqlSession。SqlSession 仍然是打开的。在 Spring 这样的框架中,通常通过事务管理器来管理 SqlSession 的生命周期,确保在一个事务(或请求)的整个过程中 SqlSession 都是可用的。SqlSession 的连接。SqlSession(这通常很复杂)。总的来说,延迟加载是一个强大的工具,但它要求你对数据访问模式有清晰的理解。不是所有关联数据都适合延迟加载,关键在于平衡初始加载速度和后续数据访问的效率。
以上就是mybatis 是否支持延迟加载?延迟加载的原理是什么?的详细内容,更多请关注php中文网其它相关文章!
 
                        
                        每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
 
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号