1. 程式人生 > >Mybatis原始碼分析(7)—— 結果集處理

Mybatis原始碼分析(7)—— 結果集處理

解析封裝

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 實際遠不止這些,至於高階功能到時候用起來再具體分析吧。