从源码的角度理解并应用Mybatis的延迟加载机制与原理

215 阅读3分钟

在实际开发中,你在很多时候并不需要总是在加载用户信息的时候就一定需要加载用户的其他信息,比如订单信息,这就是所谓的延迟加载。延迟加载也称为懒加载。其含义是:暂时不用的对象不会真正加载到内存职工,直到真正需要使用该对象时,才会执行数据库查询操作,将该对象加载到内存中。这种方式能够在一定程度上减少数据库IO次数,提升系统性能。

延迟加载

Mybatis 中提供了延迟加载机制,能够帮组我们在实际开发中实现延迟加载的需求。如果一个对象的某个属性需要延迟加载,那么在映射改属性时,会为该属性创建相应的代理对象并返回;当真正要使用功能延迟加载的属性时,会通过代理对象执行数据库加载操作,得到真正的数据。

Mybatis的延迟加载方式有两种配置方式:

方式一:全局延迟加载;Mybatis-config.xml 主配置文件中提供了lazyLoadingEnabled和aggressiveLazyLoading参数来来开工至是否开启延迟加载机制。lazyLoadingEnabled参数值为true时表示开启延迟加载,否则表示不开启延迟加载。 aggressiveLazyLoading 参数用于控制ResultMap默认的加载行为,参数值为false 表示ResultMap 默认的加载行为为延迟加载,否则为积极加载;在mybatis 3.4.1 版本之后,aggressiveLazyLoading 配置默认为false,之前的版本默认值为true。

 <settings>
    <!--打开延迟加载的开关-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <!--将积极加载改为懒加载即按需加载-->
    <setting name="aggressiveLazyLoading" value="false"/>
  </settings>

方式二:局部延迟加载方式;由collection和 association 标签还提供了一个fetchType属性,用于控制级联查询的加载行为,fetchType属性值为lazy时,表示级联查询采用延迟加载方式,当fetchType属性值为eager时,表示该级联查询采用积极加载方式。

 <resultMap id="userMap" type="com.th.entity.User">
    <result column="id" property="id"></result>
    <result column="user_name" property="username"></result>
    <result column="pass_word" property="password"></result>
    <result column="bithday" property="bithday"></result>
    <collection property="orderList" ofType="com.th.entity.Order" fetchType="lazy">
      <result column="oid" property="id"></result>
      <result column="order_time" property="ordertime"></result>
      <result column="total" property="total"></result>
    </collection>

延迟加载源码

全局延迟加载是在settings标签中配置lazyLoadingEnabled 和aggressiveLazyLoading参数。在Configuration中对这两个参数进行了定义:

 /*
  aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载
  在mybatis  3.4.1 版本之后,aggressiveLazyLoading 配置默认为false,之前的版本默认值为true
   */
  protected boolean aggressiveLazyLoading;\

/*
延迟加载触发方法
 */
  protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));

/*
  是否开启延迟加载
   */
  protected boolean lazyLoadingEnabled = false;

mybatis 中与部分延迟加载相关的类有ResultLoader,ResultLoaderMap,ProxyFactory 接口及实现类。

ResultLoader

ResultLoader 主要负责保存一次延迟加载操作所需要的全部信息。其定义的核心字段为: 在这里插入图片描述 loadResult()方法是ResultLoader的核心方法,该方法通过Executor执行ResultLoader中记录SQL语句并返回相应的延迟加载对象;该方法中调用的selectList()方法是真实完成延迟加载操作的地方。 在这里插入图片描述

ResultLoaderMap

ResultLoaderMap中使用loaderMap保存对象延迟加载属性及其对应的ResultLoader对象之间的关系:

private final Map<String, LoadPair> loaderMap = new HashMap<>();

在ResultLoaderMap中提供load()和loadAll()两个执行延迟加载的入口方法,load()负责加载指定名称的属性,loadAll()加载该对象中全部的延迟加载属性,代码如下: 在这里插入图片描述 load()和loadAll()方法都调用了 pair.load()方法执行延迟加载,延迟加载核心代码如下: 在这里插入图片描述

proxyFactory

Mybatis中定义了PRoxyFactory接口以及两个实现类。其中CglibProxyFactory使用cglib方式创建代理对象,但是在Mybatis 3.5.10后就不建议使用了,默认使用JavassistProxyFactory 创建代理对象。 入口是 Configuration.setProxyFactory()方法 在这里插入图片描述 Mybatis的查询结果是由ResultSetHandler接口的handleResultSets()方法处理的。ResultSetHandler接口只有一个实现:DefaultResultSetHandler,其延迟加载的核心方式是createResultObject(),其源码如下: 在这里插入图片描述 返回结果resultObject 采用了默认的javassistProx的代理对象:

在这里插入图片描述

javassistProxy的具体如下:

 /**
   * JavassistProxyFactory接口实现
   * @param target 目标结果对象
   * @param lazyLoader 延迟加载对象
   * @param configuration 配置
   * @param objectFactory 对象工厂
   * @param constructorArgTypes 构造参数类型
   * @param constructorArgs  构造参数值
   * @return
   */
  @Override
  public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
  }


代理对象的具体实现是由javassistProxy 定义的内部类EnhancedResultObjectProxyImpl 来实现的,EnhancedResultObjectProxyImpl 继承的是MethodHandler接口。核心代码如下: 在这里插入图片描述

DefaultResultSetHandler

DefaultResultSetHandler.getNestedQueryMappingValue ()方法处理嵌套查询,如果开启了延迟加载功能,则创建相对应的resultLoader对象并返回DEFERRED标识对象,如果未开启延迟加载功能,则直接执行嵌套查询,并返回结果,代码如下: 在这里插入图片描述 DefaultResultSetHandler.getNestedQueryMappingValue ()方法创建结果对象,其中会遍历ResultMappropertyResultMappings集合,如果存在嵌套查询且配置了延迟加载,则为结果对象代理对象并将该代理对象返回。代码如下: 在这里插入图片描述 总结: mybatis中处理的延迟加载属性流程可以用下图表示: 在这里插入图片描述