Mybatis延迟加载使用及原理

MyBatis的延迟加载是指当需要访问一个对象的关联对象时,并不是在查询主对象的同时就加载这些关联对象,而是在实际使用到关联对象时才去查询加载。这样做的好处是可以提升查询的性能,特别是对于关联关系较为复杂,数据量较大的情况。

使用延迟加载

在MyBatis配置文件中可以设置延迟加载的属性:

xml 复制代码
<settings>
  <!-- 启用延迟加载 -->
  <setting name="lazyLoadingEnabled" value="true"/>
  <!-- 当使用到关联对象的某个属性时才加载该对象 -->
  <setting name="aggressiveLazyLoading" value="false"/>
</settings>

在映射文件中,可以通过fetchType="lazy"来指定延迟加载:

xml 复制代码
<!-- 用户和订单的一对多关系 -->
<resultMap id="UserResultMap" type="User">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  <collection property="orders" column="user_id" select="selectOrders" fetchType="lazy"/>
</resultMap>

<select id="selectUser" resultMap="UserResultMap">
  SELECT * FROM user WHERE id = #{id}
</select>

<select id="selectOrders" resultMap="OrderResultMap">
  SELECT * FROM order WHERE user_id = #{userId}
</select>

延迟加载的原理

  1. 代理对象创建:MyBatis对于需要延迟加载的对象会创建一个动态代理,在你访问这个代理对象的时候,实际上会触发代理逻辑。

  2. 代理逻辑触发 :当你访问这个代理对象的某个方法时,比如getOrders(),动态代理会拦截这个调用,检查该对象的orders属性是否已经加载。

  3. SQL查询执行 :如果orders属性未加载,代理逻辑会使用之前保存的参数信息,执行对应的SQL语句来查询数据,然后将结果设置到代理对象的orders属性中。

  4. 属性设置与返回 :结果被设置之后,就可以像访问普通对象一样访问orders属性了,从此以后,对这个属性的访问不会再触发SQL查询。

源码分析

在MyBatis的源码中,延迟加载的实现涉及以下几个核心组件:

  • SqlSession:数据库会话,执行持久化操作。
  • Executor:执行器,负责SQL语句的生成和查询缓存的维护。
  • Configuration:配置信息,包含了是否启用延迟加载的设置。
  • ResultLoader:结果加载器,负责延迟加载逻辑。

延迟加载主要通过ResultLoaderloadResult方法实现:

java 复制代码
public class ResultLoader {
  // ...省略其他代码...
  
  public Object loadResult() throws SQLException {
    // 具体的查询逻辑
    List<Object> list = selectList(statement, parameter, rowBounds, executor);
    Object value = valueHandler.getResult(list);
    if (value != null && configuration.isLazyLoadingEnabled()) {
      // 如果配置了懒加载,设置已加载标志
      lazyLoader.loaded(property);
    }
    return value;
  }
}

当访问一个代理对象的方法时,MyBatis会检查是否已经加载了相应的属性。如果没有,就会调用ResultLoader.loadResult()方法来执行实际的SQL查询,并将结果加载到对象中。

代码演示

假设有一个User类和Order类,并且在User类中有一个List<Order>类型的orders属性。当访问user.getOrders()时,如果orders尚未加载,MyBatis会执行对应的查询语句,并返回订单列表。

java 复制代码
public class User {
  private Integer id;
  private String name;
  // 此属性通过MyBatis懒加载
  private List<Order> orders;

  // getters and setters...
}

细节详尽

延迟加载的实现细节主要关注以下几点:

  • 代理方式:MyBatis可以使用JDK动态代理或CGLIB来创建代理对象。
  • 触发条件:延迟加载的触发条件是访问代理对象的属性或方法。
  • 会话管理 :延迟加载发生时,必须保证原始的SqlSession还是开放状态,否则无法执行查询。
  • 线程安全:MyBatis的延迟加载不是线程安全的。两个线程同时访问同一个代理对象的同一个延迟加载属性可能会导致查询执行两次。

延迟加载为性能优化提供一种手段,但也增加了应用的复杂性。在使用时,应当权衡延迟加载带来的好处与可能出现的问题。

相关推荐
why1516 分钟前
腾讯(QQ浏览器)后端开发
开发语言·后端·golang
浪裡遊9 分钟前
跨域问题(Cross-Origin Problem)
linux·前端·vue.js·后端·https·sprint
声声codeGrandMaster17 分钟前
django之优化分页功能(利用参数共存及封装来实现)
数据库·后端·python·django
呼Lu噜1 小时前
WPF-遵循MVVM框架创建图表的显示【保姆级】
前端·后端·wpf
bing_1581 小时前
为什么选择 Spring Boot? 它是如何简化单个微服务的创建、配置和部署的?
spring boot·后端·微服务
学c真好玩1 小时前
Django创建的应用目录详细解释以及如何操作数据库自动创建表
后端·python·django
Asthenia04121 小时前
GenericObjectPool——重用你的对象
后端
Piper蛋窝1 小时前
Go 1.18 相比 Go 1.17 有哪些值得注意的改动?
后端
excel2 小时前
招幕技术人员
前端·javascript·后端
盖世英雄酱581362 小时前
什么是MCP
后端·程序员