美文网首页
MyBatis印象阅读之延迟加载

MyBatis印象阅读之延迟加载

作者: 向光奔跑_ | 来源:发表于2019-08-16 14:35 被阅读0次

    今天的我们已经没有欠下技术债了,所以我们来探讨下关于MyBatis的延迟加载。

    首先我们来看官网的说明:

    MyBatis 能够对嵌套查询进行延迟加载,因此可以将大量语句同时运行的开销分散开来。 然而,如果你加载记录列表之后立刻就遍历列表以获取嵌套的数据,就会触发所有的延迟加载查询,性能可能会变得很糟糕。

    这里我需要着重说明一点:
    延迟加载在Mybatis中不是可以针对每一种情况的,它必须是在嵌套返回的情况下才能够进行使用。

    有了下面的说明后,我们可以返回在解析返回结果时,之前我们没有说明的嵌套解析部分:

      public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
        //处理嵌套映射的情况
        if (resultMap.hasNestedResultMaps()) {
          ensureNoRowBounds();
          checkResultHandler();
          handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        } else {
          //处理简单映射情况
          handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        }
      }
    

    下面开始我们分析。

    1. 嵌套映射处理分析

    这里我们先来看有2个判断条件,比较简单,我们直接过一下就行:

      private void ensureNoRowBounds() {
        if (configuration.isSafeRowBoundsEnabled() && rowBounds != null && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) {
          throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. "
              + "Use safeRowBoundsEnabled=false setting to bypass this check.");
        }
      }
    
      protected void checkResultHandler() {
        if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) {
          throw new ExecutorException("Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. "
              + "Use safeResultHandlerEnabled=false setting to bypass this check "
              + "or ensure your statement returns ordered data and set resultOrdered=true on it.");
        }
      }
    

    这里也不用太过关注,我们重点放在主要逻辑上:

    private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
        final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
        ResultSet resultSet = rsw.getResultSet();
        skipRows(resultSet, rowBounds);
        Object rowValue = previousRowValue;
        while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
          final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
          final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
          Object partialObject = nestedResultObjects.get(rowKey);
          // issue #577 && #542
          if (mappedStatement.isResultOrdered()) {
            if (partialObject == null && rowValue != null) {
              nestedResultObjects.clear();
              storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
            }
            rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
          } else {
            rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
            if (partialObject == null) {
              storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
            }
          }
        }
        if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
          previousRowValue = null;
        } else if (rowValue != null) {
          previousRowValue = rowValue;
        }
      }
    
    

    这里延迟加载的深度比较深,我们简单的过一下:
    从getRowValue-> createResultObject->createParameterizedResultObject->getNestedQueryConstructorValue

    这里的过程会比较的绕很烦,我目前是打算理解其意即可,所以我们重点放在getNestedQueryConstructorValue方法上:

     private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix) throws SQLException {
        final String nestedQueryId = constructorMapping.getNestedQueryId();
        final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
        final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
        final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, nestedQueryParameterType, columnPrefix);
        Object value = null;
        if (nestedQueryParameterObject != null) {
          final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
          final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
          final Class<?> targetType = constructorMapping.getJavaType();
          final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
          value = resultLoader.loadResult();
        }
        return value;
      }
    

    这里会出现一个新的类ResultLoader,专门是和延迟加载相关的,我们来看下

    2. ResultLoader解析

    我们先来看属性和构造方法:

    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;
        //如果当前线程不是创建线程,则调用 #newExecutor() 方法,创建 Executor 对象,因为 Executor 是非线程安全的。
        if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) {
          localExecutor = newExecutor();
        }
        try {
          return localExecutor.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);
      }
    

    这里的逻辑就是额外使用localExecutor查询

    接下来我们在进入另一个类ResultExtractor:

    3.ResultExtractor解析

    我们直接整体来看:

    public class ResultExtractor {
      private final Configuration configuration;
      private final ObjectFactory objectFactory;
    
      public ResultExtractor(Configuration configuration, ObjectFactory objectFactory) {
        this.configuration = configuration;
        this.objectFactory = objectFactory;
      }
    
      public Object extractObjectFromList(List<Object> list, Class<?> targetType) {
        Object value = null;
        //情况一,targetType 就是 list ,直接返回
        if (targetType != null && targetType.isAssignableFrom(list.getClass())) {
          value = list;
          // 情况二,targetType 是集合,添加到其中
        } else if (targetType != null && objectFactory.isCollection(targetType)) {
          value = objectFactory.create(targetType);
          MetaObject metaObject = configuration.newMetaObject(value);
          metaObject.addAll(list);
          // 情况三,targetType 是数组
        } else if (targetType != null && targetType.isArray()) {
          Class<?> arrayComponentType = targetType.getComponentType();
          Object array = Array.newInstance(arrayComponentType, list.size());
          if (arrayComponentType.isPrimitive()) {
            for (int i = 0; i < list.size(); i++) {
              Array.set(array, i, list.get(i));
            }
            value = array;
          } else {
            value = list.toArray((Object[])array);
          }
          // 情况四,普通对象,取首个对象
        } else {
          if (list != null && list.size() > 1) {
            throw new ExecutorException("Statement returned more than one row, where no more than one was expected.");
          } else if (list != null && list.size() == 1) {
            value = list.get(0);
          }
        }
        return value;
      }
    }
    

    4. 今日总结

    今天其实我自己也不是太搞懂,关于这块的封装有点深,而且相关地方也比较多,就大概看一下吧。具体没有深入了解MyBatis的运作机制。允许我今天的水~~~

    相关文章

      网友评论

          本文标题:MyBatis印象阅读之延迟加载

          本文链接:https://www.haomeiwen.com/subject/tztfsctx.html