四时宝库

程序员的知识宝库

MyBatis插件原理及应用四-懒加载实现原理

MyBatis支持通过<collection>或者<association>标签关联一个外部的查询Mapper,当通过MyBatis配置开启懒加载机制时,执行查询操作不会触发关联的查询Mapper,而通过Getter方法访问实体属性时才会执行一次关联的查询Mapper,然后为实体属性赋值。本节我们就来了解MyBatis懒加载机制的实现原理。

在DefaultResultSetHandler类的handleRowValues()方法中处理结果集时,对嵌套的ResultMap和非嵌套ResultMap做了不同处理,代码如下:

  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    // 是否有嵌套ResultMap
    if (resultMap.hasNestedResultMaps()) {
      // 嵌套查询校验RowBounds,可以通过设置safeRowBoundsEnabled=false参数绕过校验
      ensureNoRowBounds();
      // 校验ResultHandler,可以设置safeResultHandlerEnabled=false参数绕过校验
      checkResultHandler();
      // 如果有嵌套的ResultMap,调用handleRowValuesForNestedResultMap处理嵌套ResultMap
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      // 如果无嵌套的ResultMap,调用handleRowValuesForSimpleResultMap处理简单非嵌套ResultMap
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

在介绍ResultMap时已经介绍过,当使用<association>和<collection>标签关联一个外部的查询Mapper时,ResultMap对象的hasNestedResultMaps属性值为false,hasNestedQueries属性值为true。因此,MyBatis框架在开启懒加载机制后,handleRowValues()方法会调用handleRowValuesForSimpleResultMap()方法处理ResultMap映射。该方法实现如下:

  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
    skipRows(rsw.getResultSet(), rowBounds);
    // 遍历处理每一行记录
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      // 对<discriminator>标签配置的鉴别器进行处理,获取实际映射的ResultMap对象
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      // 调用getRowValue()把一行数据转换为Java实体对象
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }

如上面的代码所示,在handleRowValuesForSimpleResultMap()方法中,首先调用skipRows()方法跳过RowBounds对象指定偏移的行,然后遍历结果集中所有的行,对<discriminator>标签配置的鉴别器进行处理,获取实际映射的ResultMap对象,接着调用getRowValue()方法处理一行记录,将数据库行记录转换为Java实体对象。getRowValue()方法实现如下:


  // 处理非嵌套ResultMap
  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    // 创建ResultLoaderMap对象,用于存放懒加载属性信息
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 创建ResultMap指定的类型实例,通常为<resultMap>标签的type属性指定的类型
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
    // 判断该类型是否注册了TypeHandler
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      // 判断是否需要处理自动映射
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        // 调用applyAutomaticMappings()方法处理自动映射的字段
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      }
      // 处理<result>标签配置映射的字段
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

如上面的代码所示,在getRowValue()方法中主要做了下面几件事情:

(1)创建ResultLoaderMap对象,该对象用于存放懒加载的属性及对应的ResultLoader对象,MyBatis中的ResultLoader用于执行一个查询Mapper,然后将执行结果赋值给某个实体对象的属性。

(2)调用createResultObject()方法创建ResultMap对应的Java实体对象,我们需要重点关注该方法的实现,代码如下:

  // 初始化返回的实体对象,并处理构造方法映射
  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<Class<?>>();
    final List<Object> constructorArgs = new ArrayList<Object>();
    // 调用createResultObject()方法创建结果对象
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      // 获取<result>结果集映射信息
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      for (ResultMapping propertyMapping : propertyMappings) {
        // 如果映射中配置了懒加载,则创建代理对象
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
          // 调用ProxyFactory实例的createProxy()方法创建代理对象
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
          break;
        }
      }
    }
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
    return resultObject;
  }

如上面的代码所示,createResultObject()方法中,首先调用重载的createResultObject()方法使用ObjectFactory对象创建Java实体对象,然后判断ResultMap中是否有嵌套的查询,如果有嵌套的查询并且开启了懒加载机制,则通过MyBatis中的ProxyFactory创建实体对象的代理对象。ProxyFactory接口有两种不同的实现,分别为CglibProxyFactory和JavassistProxyFactory。也就是说,MyBatis同时支持使用Cglib和Javassist创建代理对象,具体使用哪种策略创建代理对象,可以在MyBatis主配置文件中通过proxyFactory属性指定。

(3)调用applyAutomaticMappings()方法处理自动映射,该方法在介绍MyBatis级联映射原理时已经介绍过,可自行参考MyBatis源码。

(4)调用applyPropertyMappings()方法处理<result>标签配置的映射字段,该方法中除了为Java实体属性设置值外,还将指定了懒加载的属性添加到ResultLoaderMap对象中。

当我们开启懒加载时,执行查询Mapper返回的实际上是通过Cglib或Javassist创建的动态代理对象。假设我们指定了使用Cglig创建动态代理对象,调用动态代理对象的Getter方法时会执行MyBatis中定义的拦截逻辑,代码如下:

  public Object intercept(Object enhanced, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
      final String methodName = method.getName();
      try {
        synchronized (lazyLoader) {
          if (WRITE_REPLACE_METHOD.equals(methodName)) {
            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 CglibSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);
            } else {
              return original;
            }
          } else {
            if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {
              if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {
                lazyLoader.loadAll();
              } else if (PropertyNamer.isSetter(methodName)) {
                final String property = PropertyNamer.methodToProperty(methodName);
                lazyLoader.remove(property);
              } else if (PropertyNamer.isGetter(methodName)) {
                final String property = PropertyNamer.methodToProperty(methodName);
                if (lazyLoader.hasLoader(property)) {
                  lazyLoader.load(property);
                }
              }
            }
          }
        }
        return methodProxy.invokeSuper(enhanced, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

如上面的代码所示,EnhancedResultObjectProxyImpl是CglibProxyFactory类中的一个内部类,EnhancedResultObjectProxyImpl的intercept()方法中定义了调用动态代理对象方法的拦截逻辑。也就是说,当我们调用代理实体对象的Getter方法获取属性时,会执行EnhancedResultObjectProxyImpl类的intercept()方法中的拦截逻辑。

在EnhancedResultObjectProxyImpl类的intercept()方法中,获取Getter方法对应的属性名称,然后调用ResultLoaderMap对象的hasLoader()方法判断该属性是否是懒加载属性,如果是,则调用ResultLoaderMap对象的load()方法加载该属性,ResultLoaderMap对象的load()方法最终会调用LoadPair对象的load()方法,代码如下:

 public void load(final Object userObject) throws SQLException {
      if (this.metaResultObject == null || this.resultLoader == null) {
        if (this.mappedParameter == null) {
          throw new ExecutorException("Property [" + this.property + "] cannot be loaded because "
                  + "required parameter of mapped statement ["
                  + this.mappedStatement + "] is not serializable.");
        }

        final Configuration config = this.getConfiguration();
        final MappedStatement ms = config.getMappedStatement(this.mappedStatement);
        if (ms == null) {
          throw new ExecutorException("Cannot lazy load property [" + this.property
                  + "] of deserialized object [" + userObject.getClass()
                  + "] because configuration does not contain statement ["
                  + this.mappedStatement + "]");
        }

        this.metaResultObject = config.newMetaObject(userObject);
        this.resultLoader = new ResultLoader(config, new ClosedExecutor(), ms, this.mappedParameter,
                metaResultObject.getSetterType(this.property), null, null);
      }

      /* We are using a new executor because we may be (and likely are) on a new thread
       * and executors aren't thread safe. (Is this sufficient?)
       *
       * A better approach would be making executors thread safe. */
      if (this.serializationCheck == null) {
        final ResultLoader old = this.resultLoader;
        this.resultLoader = new ResultLoader(old.configuration, new ClosedExecutor(), old.mappedStatement,
                old.parameterObject, old.targetType, old.cacheKey, old.boundSql);
      }

      this.metaResultObject.setValue(property, this.resultLoader.loadResult());
    }

如上面的代码所示,该方法中创建了一个ResultLoader对象,然后ResultLoader对象的loadResult()方法执行查询操作,将查询结果赋值给对应的Java实体属性。ResultLoader类的loadResult()方法实现如下:

/
 * @author Clinton Begin
 */
public class ResultLoader {

  protected final Configuration configuration;
  protected final Executor executor;
  protected final MappedStatement mappedStatement;
  protected final Object parameterObject;
  protected final Class<?> targetType;
  protected final ObjectFactory objectFactory;
  protected final CacheKey cacheKey;
  protected final BoundSql boundSql;
  protected final ResultExtractor resultExtractor;
  protected final long creatorThreadId;
  
  protected boolean loaded;
  protected Object resultObject;
  
  public ResultLoader(Configuration config, Executor executor, MappedStatement mappedStatement, Object parameterObject, Class<?> targetType, CacheKey cacheKey, BoundSql boundSql) {
    this.configuration = config;
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.parameterObject = parameterObject;
    this.targetType = targetType;
    this.objectFactory = configuration.getObjectFactory();
    this.cacheKey = cacheKey;
    this.boundSql = boundSql;
    this.resultExtractor = new ResultExtractor(configuration, objectFactory);
    this.creatorThreadId = Thread.currentThread().getId();
  }

  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 {
      return localExecutor.<E> query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql);
    } finally {
      if (localExecutor != executor) {
        localExecutor.close(false);
      }
    }
  }

  private Executor newExecutor() {
    final Environment environment = configuration.getEnvironment();
    if (environment == null) {
      throw new ExecutorException("ResultLoader could not load lazily.  Environment was not configured.");
    }
    final DataSource ds = environment.getDataSource();
    if (ds == null) {
      throw new ExecutorException("ResultLoader could not load lazily.  DataSource was not configured.");
    }
    final TransactionFactory transactionFactory = environment.getTransactionFactory();
    final Transaction tx = transactionFactory.newTransaction(ds, null, false);
    return configuration.newExecutor(tx, ExecutorType.SIMPLE);
  }

  public boolean wasNull() {
    return resultObject == null;
  }

}

如上面的代码所示,在ResultLoader类的loadResult()方法中调用selectList()方法完成查询操作,selectList()方法中最终会调用Executor对象的query()方法完成查询操作。

最后我们来总结一下,MyBatis中的懒加载实际上是通过动态代理来实现的。当我们通过MyBatis的配置开启懒加载后,执行第一次查询操作实际上返回的是通过Cglig或者Javassist创建的代理对象。因此,调用代理对象的Getter方法获取懒加载属性时,会执行动态代理的拦截方法,在拦截方法中,通过Getter方法名称获取Java实体属性名称,然后根据属性名称获取对应的LoadPair对象,LoadPair对象中维护了Mapper的Id,有了Mapper的Id就可以获取对应的MappedStatement对象,接着执行一次额外的查询操作,使用查询结果为懒加载属性赋值。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接