zoukankan      html  css  js  c++  java
  • Mybatis源码分析之结果集处理

    解析封装

    ResultMap 是和结果集相关的东西,最初在解析 XML 的时候,于 parseStatementNode 方法中,针对每一个 select 节点进行解析,转换为 MappedStatement(类似 Spring 的 bean 配置和 BeanDefinition 的关系)。

    在 MapperBuilderAssistant 的 addMappedStatement 方法中,构建完statementBuilder,会调用 setStatementResultMap 方法给其设置 ResultMap。

    setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);

    private void setStatementResultMap(
          String resultMap,
          Class<?> resultType,
          ResultSetType resultSetType,
          MappedStatement.Builder statementBuilder) {
        resultMap = applyCurrentNamespace(resultMap, true);
    
        List<ResultMap> resultMaps = new ArrayList<ResultMap>();
        if (resultMap != null) {
          String[] resultMapNames = resultMap.split(",");
          for (String resultMapName : resultMapNames) {
            try {
              resultMaps.add(configuration.getResultMap(resultMapName.trim()));
            } catch (IllegalArgumentException e) {
              throw new IncompleteElementException("Could not find result map " + resultMapName, e);
            }
          }
        } else if (resultType != null) {
          ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(
              configuration,
              statementBuilder.id() + "-Inline",
              resultType,
              new ArrayList<ResultMapping>(),
              null);
          resultMaps.add(inlineResultMapBuilder.build());
        }
        statementBuilder.resultMaps(resultMaps);
    
        statementBuilder.resultSetType(resultSetType);
    }

    首先检查 resultMap,根据名称去 configuration 中取出对应的 resultMap 放到集合中;

    如果 resultMap 不存在,就检查 resultType ,然后利用这个 resultType 构造一个 resultMap,如果两个都没有,那就走着瞧(resultMaps集合为空)。

    中期调用

    在 PreparedStatementHandler 的 query 方法中:

    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return resultSetHandler.<E> handleResultSets(ps);
    }
    

    而 resultSetHandler 于基类 BaseStatementHandler 中构造:

    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

    根据是否有嵌套的 ResultMaps 来确定创建 NestedResultSetHandler 还是 FastResultSetHandler:

    public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
          ResultHandler resultHandler, BoundSql boundSql) {
        ResultSetHandler resultSetHandler = mappedStatement.hasNestedResultMaps() ? new NestedResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql,
            rowBounds) : new FastResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
    }

    影响这个判断的有几个点:

    • 解析时期
    public Builder resultMaps(List<ResultMap> resultMaps) {
      mappedStatement.resultMaps = resultMaps;
      for (ResultMap resultMap : resultMaps) {
        mappedStatement.hasNestedResultMaps = mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps();
      }
      return this;
    }
    • getBoundSql时期
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    后期处理

    NestedResultSetHandler继承自FastResultSetHandler ,二者在handleResultSets是一致的(只是重写了 handleRowValues 等方法):

    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        final List<Object> multipleResults = new ArrayList<Object>();
        final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        int resultSetCount = 0;
        ResultSet rs = stmt.getResultSet();
    
        while (rs == null) {
          // move forward to get the first resultset in case the driver
          // doesn't return the resultset as the first result (HSQLDB 2.1)
          if (stmt.getMoreResults()) {
            rs = stmt.getResultSet();
          } else {
            if (stmt.getUpdateCount() == -1) {
              // no more results.  Must be no resultset
              break;
            }
          }
        }
        // 验证是否定义了resultType或者resultMap
        validateResultMapsCount(rs, resultMapCount);
        while (rs != null && resultMapCount > resultSetCount) {
          final ResultMap resultMap = resultMaps.get(resultSetCount);
          ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
          handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
          rs = getNextResultSet(stmt);
          cleanUpAfterHandlingResultSet();
          resultSetCount++;
        }
        return collapseSingleResultList(multipleResults);
    }

    处理模板

    • validateResultMapsCount

    resultMapCount 就是之前解析时构造的那个 ResultMap 集合的 size,如果没有配置 ResultMap 或 ResultType,这里就要报错了:

    A query was run and no Result Maps were found for the Mapped Statement ……

    • handleResultSet

    传入构建的List类型的集合 multipleResults,调用 handleRowValues 处理从 ResultSet 获取的结果集。

    • collapseSingleResultList

    这个就不说了。

    细节分析

    FastResultSetHandler 用得相对较多,这里只针对其 handleRowValues 处理结果行进行分析:

    protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
        final DefaultResultContext resultContext = new DefaultResultContext();
        skipRows(rs, rowBounds);
        while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
          final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
          Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
          callResultHandler(resultHandler, resultContext, rowValue);
        }
    }

    1、RowBounds 与 分页

    skipRows 就是跳过行,基于查询结果的分页使用,其中会直接调用 ResultSet 的 absolute 方法:

    rs.absolute(rowBounds.getOffset());

    而 shouldProcessMoreRows 方法就是判断是否应该获取更多的行:

    protected boolean shouldProcessMoreRows(ResultSet rs, ResultContext context, RowBounds rowBounds) throws SQLException {
        return !context.isStopped() && rs.next() && context.getResultCount() < rowBounds.getLimit();
    }

    也对应了分页的limit字段,这种分页可以说是伪分页,查出来再分页。所以我们一般使用插件的形式来实现分页,基于sql的动态替换。

    2、获取行数据

    如果应该获取更多的行,就会逐行处理结果:

    protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
        final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
        Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
        if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
          final MetaObject metaObject = configuration.newMetaObject(resultObject);
          boolean foundValues = resultMap.getConstructorResultMappings().size() > 0;
          if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
            final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
            foundValues = applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache) || foundValues;
          }
          final List<String> mappedColumnNames = resultColumnCache.getMappedColumnNames(resultMap, null);
          foundValues = applyPropertyMappings(rs, resultMap, mappedColumnNames, metaObject, lazyLoader, null) || foundValues;
          foundValues = (lazyLoader != null && lazyLoader.size() > 0) || foundValues;
          resultObject = foundValues ? resultObject : null;
          return resultObject;
        }
        return resultObject;
    }

    主要就是根据相关参数创建结果对象,这里又分多钟情况:

    • 原始对象的处理(如 Integer 等基本类型对象)

    利用 ResultMap 中存储的返回结果类型信息,直接返回相应的值。

    • 非原始对象(是自定义的对象)处理

    运用反射构造实体对象(相当于一个带有零值的初始对象)。然后用该对象构造一个 MetaObject ,并查看是否需要根据映射自动填充对象。

    如果需要通过自动映射来填充,取出 List 类型的属性名,根据这个同 SQL 中查出来一致的字段名,去 ResultSet 中拿出来然后填充到对象中,相关代码如下:

    final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
    if (property != null) {
        final Class<?> propertyType = metaObject.getSetterType(property);
        if (typeHandlerRegistry.hasTypeHandler(propertyType)) {
           final TypeHandler<?> typeHandler = resultColumnCache.getTypeHandler(propertyType, columnName);
           final Object value = typeHandler.getResult(rs, columnName);
           if (value != null || configuration.isCallSettersOnNulls()) { // issue #377, call setter on nulls
              metaObject.setValue(property, value);
              foundValues = true;
           }
        }
    }

    这个处理完后,才是非映射列的处理。

    3、结果映射

    resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来, 并在一些情形下允许你做一些 JDBC 不支持的事情。

    在 handleResultSets 方法中,取 ResultSet 元信息构造了一个 ResultColumnCache:

    ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);

    后面在映射列的时候分别调用了 getUnmappedColumnNames 和 getMappedColumnNames 方法来取列集合,对应于 ResultColumnCache 的 unMappedColumnNamesMap 和 mappedColumnNamesMap。如果集合为空,将调用下面的方法来构造:

    private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
        List<String> mappedColumnNames = new ArrayList<String>();
        List<String> unmappedColumnNames = new ArrayList<String>();
        final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
        final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
        for (String columnName : columnNames) {
            final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
            if (mappedColumns.contains(upperColumnName)) {
              mappedColumnNames.add(upperColumnName);
            } else {
              unmappedColumnNames.add(columnName);
            }
        }
        mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
        unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
    }

    通常情况下,loadMappedAndUnmappedColumnNames 只会调用一次(多行结果的列名是一样的),所以 ResultColumnCache 名副其实。

    它区分是否是满足映射的列名是根据 mappedColumns 集合中是否有来判断的,而 mappedColumns 对应于 ResultMap 配置中的 column 属性。

    所以自动映射针对的是没有配置 ResultMap 而使用了 ResultType 的情况;或者配置并使用了 ResultMap,但是 ResultMap 中的 column 属性集合不包含实际SQL字段名,也就是对不上的情况。这个时候是需要直接操作这个字段名,可能需要去除下划线等基于规则的演变,最后通过 MetaObject 来赋值。

    那么非自动映射呢?就是配置并使用了 ResultMap,这个简单多了,直接按你配置的来,取 ResultMap 属性配置下的 property 名称,这个就是你实际的对象属性名,然后也是通过 MetaObject 来赋值。

    分析到这里,已经比较明朗了,整体的脉络也在脑海中勾勒出来了。ResultMap 实际远不止这些,至于高级功能到时候用起来再具体分析吧。

  • 相关阅读:
    递归
    二叉树
    IIs 网站应用程序与虚拟目录的区别及高级应用说明(文件分布式存储方案)
    Python时间,日期,时间戳之间转换
    jquery 时间戳和日期时间转化
    javascript 中解析json
    设计模式之单例模式
    深入理解DIP、IoC、DI以及IoC容器
    《大型网站技术架构》读书笔记一:大型网站架构演化
    Key/Value之王Memcached初探:二、Memcached在.Net中的基本操作
  • 原文地址:https://www.cnblogs.com/lucare/p/9312612.html
Copyright © 2011-2022 走看看