Mybatis Lazy Loading(懒加载) 实现原理

在上一篇Mybatis查询逻辑时候,有一个点就是懒加载,这个点其实有点复杂,所以博主单独拿出来分析。

本文从以下角度展开:

  1. 什么是懒加载?
  2. Mybatis对懒加载的配置如何?
  3. 懒加载通过什么方式实现懒加载的?

懒加载使用

  1. 配置中加入以下两行:
        <!-- 打开延迟加载的开关 -->
        <setting name="lazyLoadingEnabled" value="true" />
        <!-- 将积极加载改为消极加载(及按需加载) -->
        <setting name="aggressiveLazyLoading" value="false" />
  1. 在所需要的resultMap 的 列上加上 fetchType="lazy" 表明是懒加载
    例如:
  <resultMap id="LazyResultMap" type="anla.learn.mybatis.interceptor.model.UserLazyDepartment" >
    <id column="uid" property="uid" jdbcType="INTEGER" />
    <result column="email" property="email" jdbcType="VARCHAR" />
    <result column="password" property="password" jdbcType="VARCHAR" />
    <result column="department_id" property="departmentId" jdbcType="BIGINT"/>
    <!-- 懒加载 -->
    <association property="department" fetchType="lazy" select="anla.learn.mybatis.interceptor.dao.DepartmentMapper.getByIndex" javaType="anla.learn.mybatis.interceptor.model.Department" column="department_id"/>
  </resultMap>

具体看一看例子:
https://github.com/anLA7856/mybatislearn/blob/master/mybatis-interceptor/src/test/java/MybatisTest.java

分析

从结果入手,使用懒加载和不使用懒加载的返回的对象有什么区别呢?

  1. 正常查询:
    在这里插入图片描述
  2. 定义懒加载查询:
    在这里插入图片描述

两幅图片比较明显,懒加载返回对象不是原本的对象类型,而是带有后缀的字节码动态生成的类。

那么现在的目的就是找出懒加载为返回对象为何是动态生成字节码类?

ResultSet处理

配置懒加载是,在<resultMap>增加<association> 节点配置,那么是否为处理结果发现了 fetchType=lazy 的配置,从而动态生成了类,从而当返回对象调用某些方法时,执行 懒加载查询语句呢?
efaultResultSetHandlerhandlerResultSet 开始,而后往下一步步 调试

  1. handleResultSet(rsw, resultMap, multipleResults, null);
  2. handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
  3. handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  4. Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
  5. Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);

最终要返回的object 对象是由createResultObject 方法生成:

  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    this.useConstructorMappings = false; // reset previous mapping result
    final List<Class<?>> constructorArgTypes = new ArrayList<>();
    final List<Object> constructorArgs = new ArrayList<>();
    // 首先创建一个正常的返回对象
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      for (ResultMapping propertyMapping : propertyMappings) {
        // issue gcode #109 && issue #149
        // 判断是否懒加载
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
          break;
        }
      }
    }
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
    return resultObject;
  }

上面逻辑可以看出,默认会生成一个 正常的对应 Type 的 resultObject,而当判断有嵌套查询或者有懒加载变量时,则会对已有的 resultObject 重新赋值:
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
看看这个懒加载的代理生成过程:

  @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);
  }
    public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    // 获取返回类型
      final Class<?> type = target.getClass();
      // 构造callback,这个是精髓
      EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
      // 使用Javassist创建代理
      Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);
      // 拷贝属性
      PropertyCopier.copyBeanProperties(type, target, enhanced);
      return enhanced;
    }

上面代码可以使用以下逻辑:

  1. 构造 代理 callback 即 EnhancedResultObjectProxyImpl
  2. crateProxy(type, callback, constructorArgTypes, constructorArgs); 为使用 javassist 创建。
  3. PropertyCopier.copyBeanProperties(type, target, enhanced); 为将 target 属性拷贝到enhanced中。

看看 EnhancedResultObjectProxyImpl
它实现了 javassist.util.proxy.MethodHandler, 所以实际上返回对象每次调用方法,都会调用 EnhancedResultObjectProxyImpl 的invoke方法,也就是说每个方法都被拦截了:
invoke:

    @Override
    public Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {
      final String methodName = method.getName();
      try {
        synchronized (lazyLoader) {
        // 对 lazyLoader 加锁
          if (WRITE_REPLACE_METHOD.equals(methodName)) {
          // 如果是 writeReplace 方法
            Object original;
            if (constructorArgTypes.isEmpty()) {
            // 创建对象
              original = objectFactory.create(type);
            } else {
            // 使用构造器创建对象
              original = objectFactory.create(type, constructorArgTypes, constructorArgs);
            }
            PropertyCopier.copyBeanProperties(type, enhanced, original);
            if (lazyLoader.size() > 0) {
            // 如果仍然有为执行懒加载,则需要适配
              return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
            } else {
            // 否则直接返回创建成的对象
              return original;
            }
          } else {
            if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
            // 如果有懒加载并且执行的方法不为 finalize
              if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
              // 如果 aggressiveLazyLoading 为true 或者 包含 "equals", "clone", "hashCode", "toString" 之一,则全部加载
                lazyLoader.loadAll();
              } else if (PropertyNamer.isSetter(methodName)) {
              // 如果是setter 方法,那么清楚懒加载map
                final String property = PropertyNamer.methodToProperty(methodName);
                lazyLoader.remove(property);
              } else if (PropertyNamer.isGetter(methodName)) {
              // 如果是get,那么判断是否为懒加载 所需loader ,是就执行懒加载
                final String property = PropertyNamer.methodToProperty(methodName);
                if (lazyLoader.hasLoader(property)) {
                  lazyLoader.load(property);
                }
              }
            }
          }
        }
        // 执行真正方法
        return methodProxy.invoke(enhanced, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }

上面 代码有以下逻辑:

  1. 对懒加载 map 加锁
  2. 判断是否为 writeReplace 序列化用方法,是的话在序列化成文件时候,需要判断是否加入 懒加载map集合对象。
  3. 判断当前对象 lazyLoader 是否有值,即是否有未完成的懒加载查询。lazyLoader 是在 getRowValue中赋值的,也就是说在创建代理对象时 lazyLoader 为空,在后面给代理对象每个字段进行属性赋值时,会判断是否为懒加载(lazy loading),从而设置lazyLoader的值。
    lazyLoader 中加载过属性不会加载第二次,会从 lazyLoader 中删除,所以不用担心每次都会重新查询。
  4. 判断是否配置了 aggressiveLazyLoadingtrue, aggressiveLazyLoading 为true则默认触发一个懒加载时会将所有都加载出来,或者 包含 “equals”, “clone”, “hashCode”, “toString” 之一,则全部加载。
  5. 如果是setXxx方法,则会不进行懒加载操作,并且将 lazyLoader 对应字段删除。
  6. 如果是getXxx方法,则会从lozyLoader 执行sql加载出来。

ResultLoaderMap 中维护了一个 Map<String, LoadPair>, key 为属性,而LoadPair则作为一个句柄去调用加载的语句 等语句。

最终到 LoadPairload 方法时,实际只会进行 select 操作。
LoadPair 属于 ResultLoaderMap 的内部类,最终会调用 ResultLoaderloadResult 方法:

  public Object loadResult() throws SQLException {
    List<Object> list = selectList();
    resultObject = resultExtractor.extractObjectFromList(list, targetType);
    return resultObject;
  }

  private <E> List<E> selectList() throws SQLException {
    Executor localExecutor = executor;
    if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
    // 如果不是当前线程,则新建执行器
      localExecutor = newExecutor();
    }
    try {
    // 否则使用该执行其执行query方法
      return localExecutor.query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
    } finally {
      if (localExecutor != executor) {
        localExecutor.close(false);
      }
    }
  }

loadResult 则是获取 Executor 而后执行 query 逻辑。

但是有意思的是,懒加载 使用 <association/> 节点,虽然默认行为是 select 动作,但事实上你可以方任意一个 查询,可以为 selectinsertdeleteupdate 等语句,仍然会以懒加载机制,到该执行时候会被执行。

觉得博主写的有用,不妨关注博主公众号: 六点A君。
哈哈哈,一起研究Mybatis:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值