精盡MyBatis原始碼分析 - SQL執行過程(三)之 ResultSetHandler

月圓吖發表於2020-11-25

該系列文件是本人在學習 Mybatis 的原始碼過程中總結下來的,可能對讀者不太友好,請結合我的原始碼註釋(Mybatis原始碼分析 GitHub 地址Mybatis-Spring 原始碼分析 GitHub 地址Spring-Boot-Starter 原始碼分析 GitHub 地址)進行閱讀

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

MyBatis的SQL執行過程

在前面一系列的文件中,我已經分析了 MyBatis 的基礎支援層以及整個的初始化過程,此時 MyBatis 已經處於就緒狀態了,等待使用者發號施令了

那麼接下來我們來看看它執行SQL的整個過程,該過程比較複雜,涉及到二級快取,將返回結果轉換成 Java 物件以及延遲載入等等處理過程,這裡將一步一步地進行分析:

MyBatis中SQL執行的整體過程如下圖所示:

SQLExecuteProcess

在 SqlSession 中,會將執行 SQL 的過程交由Executor執行器去執行,過程大致如下:

  1. 通過DefaultSqlSessionFactory建立與資料庫互動的 SqlSession “會話”,其內部會建立一個Executor執行器物件
  2. 然後Executor執行器通過StatementHandler建立對應的java.sql.Statement物件,並通過ParameterHandler設定引數,然後執行資料庫相關操作
  3. 如果是資料庫更新操作,則可能需要通過KeyGenerator先設定自增鍵,然後返回受影響的行數
  4. 如果是資料庫查詢操作,則需要將資料庫返回的ResultSet結果集物件包裝成ResultSetWrapper,然後通過DefaultResultSetHandler對結果集進行對映,最後返回 Java 物件

上面還涉及到一級快取二級快取延遲載入等其他處理過程

SQL執行過程(三)之ResultSetHandler

高能預警 ❗️ ❗️ ❗️ ❗️ ❗️ ❗️

DefaultResultSetHandler(結果集處理器)將資料庫查詢結果轉換成 Java 物件是一個非常繁瑣的過程,需要處理各種場景,如果繼續往下看,請做好心理準備??

可以先跳轉到 DefaultResultSetHandler,檢視流程圖

在前面SQL執行過程一系列的文件中,已經詳細地分析了在MyBatis的SQL執行過程中,SqlSession會話將資料庫操作交由Executor執行器去完成,然後通過StatementHandler去執行資料庫相關操作,並獲取到資料庫的執行結果

如果是資料庫查詢操作,則需要通過ResultSetHandler對查詢返回的結果集進行對映處理,轉換成對應的Java物件,算是SQL執行過程的最後一步,那麼我們來看看MyBatis是如何完成這個繁雜的解析過程的

ResultSetHandler介面的實現類如下圖所示:

ResultSetHandler

先回顧一下ResultSetHandler在哪被呼叫,在PreparedStatementHandlerquery方法中,程式碼如下:

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 執行
    ps.execute();
    // 結果處理器並返回結果
    return resultSetHandler.handleResultSets(ps);
}
  • 屬性resultSetHandler預設為DefaultResultSetHandler物件,可以回到《SQL執行過程(二)之StatementHandler》BaseStatementHandler小節中的構造方法的第3步可以看到
  • 呼叫resultSetHandlerhandleResultSets(Statement stmt)方法,對結果集進行對映,轉換成Java物件並返回

ResultSetWrapper

因為在DefaultResultSetHandler中,對ResultSet的操作更多的是它的ResultSetWrapper包裝類,所以我們先來看看這個類

org.apache.ibatis.executor.resultset.ResultSetWrapperjava.sql.ResultSet的包裝類,為DefaultResultSetHandler提供許多便捷的方法,直接來看它的程式碼

構造方法

public class ResultSetWrapper {

  /**
   * ResultSet 物件
   */
  private final ResultSet resultSet;
  /**
   * 型別處理器登錄檔
   */
  private final TypeHandlerRegistry typeHandlerRegistry;
  /**
   * ResultSet 中每列的列名
   */
  private final List<String> columnNames = new ArrayList<>();
  /**
   * ResultSet 中每列對應的 Java Type
   */
  private final List<String> classNames = new ArrayList<>();
  /**
   * ResultSet 中每列對應的 Jdbc Type
   */
  private final List<JdbcType> jdbcTypes = new ArrayList<>();
  /**
   * 記錄每列對應的 TypeHandler 物件
   * key:列名
   * value:TypeHandler 集合
   */
  private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<>();
  /**
   * 記錄了被對映的列名
   * key:ResultMap 物件的 id {@link #getMapKey(ResultMap, String)}
   * value:ResultMap 物件對映的列名集合
   */
  private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
  /**
   * 記錄了未對映的列名
   * key:ResultMap 物件的 id {@link #getMapKey(ResultMap, String)}
   * value:ResultMap 物件未被對映的列名集合
   */
  private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();

  public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
    super();
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.resultSet = rs;
    // 獲取 ResultSet 的元資訊
    final ResultSetMetaData metaData = rs.getMetaData();
    final int columnCount = metaData.getColumnCount();
    for (int i = 1; i <= columnCount; i++) {
      // 獲得列名或者通過 AS 關鍵字指定列名的別名
      columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
      // 獲得該列對應的 Jdbc Type
      jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
      // 獲得該列對應的 Java Type
      classNames.add(metaData.getColumnClassName(i));
    }
  }
}
  • resultSet:被包裝的ResultSet結果集物件
  • typeHandlerRegistry:型別處理器登錄檔,因為需要進行Java Type與Jdbc Type之間的轉換
  • columnNames:結果集中的所有列名
  • classNames:結果集中的每列的對應的Java Type的名稱
  • jdbcTypes:結果集中的每列對應的Jdbc Type
  • typeHandlerMap:結果集中每列對應的型別處理器
  • mappedColumnNamesMap:儲存每個ResultMap物件中對映的列名集合,也就是我們在<resultMap />標籤下的子標籤配置的column屬性
  • unMappedColumnNamesMap:儲存每個ResultMap物件中未對映的列名集合,也就是沒有在<resultMap />標籤下配置過,但是查詢結果返回了

在構造方法中,會初始化上面的columnNamesclassNamesjdbcTypes屬性

getTypeHandler方法

getTypeHandler(Class<?> propertyType, String columnName):通過列名和Java Type獲取對應的TypeHandler型別處理器,方法如下:

public TypeHandler<?> getTypeHandler(Class<?> propertyType, String columnName) {
    TypeHandler<?> handler = null;
    // 獲取列名對應的型別處理器
    Map<Class<?>, TypeHandler<?>> columnHandlers = typeHandlerMap.get(columnName);
    if (columnHandlers == null) {
      columnHandlers = new HashMap<>();
      typeHandlerMap.put(columnName, columnHandlers);
    } else {
      handler = columnHandlers.get(propertyType);
    }
    if (handler == null) {
      // 獲取該列對應的 Jdbc Type
      JdbcType jdbcType = getJdbcType(columnName);
      // 根據 Java Type 和 Jdbc Type 獲取對應的 TypeHandler 型別處理器
      handler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
      // Replicate logic of UnknownTypeHandler#resolveTypeHandler
      // See issue #59 comment 10
      if (handler == null || handler instanceof UnknownTypeHandler) {
        // 從 ResultSet 中獲取該列對應的 Java Type 的 Class 物件
        final int index = columnNames.indexOf(columnName);
        final Class<?> javaType = resolveClass(classNames.get(index));
        if (javaType != null && jdbcType != null) {
          handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
        } else if (javaType != null) {
          handler = typeHandlerRegistry.getTypeHandler(javaType);
        } else if (jdbcType != null) {
          handler = typeHandlerRegistry.getTypeHandler(jdbcType);
        }
      }
      if (handler == null || handler instanceof UnknownTypeHandler) {
        // 最差的情況,設定為 ObjectTypeHandler
        handler = new ObjectTypeHandler();
      }
      // 將生成的 TypeHandler 存放在 typeHandlerMap 中
      columnHandlers.put(propertyType, handler);
    }
    return handler;
}

大致邏輯如下:

  1. 先從Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap屬性中獲取型別處理器
  2. 如果從快取中沒有獲取到,則嘗試根據Jdbc Type和Java Type從typeHandlerRegistry登錄檔獲取
  3. 如果還是沒有獲取到,則根據classNames中拿到結果集中該列的Java Type,然後在從typeHandlerRegistry登錄檔獲取
  4. 還是沒有獲取到,則設定為ObjectTypeHandler
  5. 最後將其放入typeHandlerMap快取中

loadMappedAndUnmappedColumnNames方法

loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix)方法,初始化mappedColumnNamesMapunMappedColumnNamesMap兩個屬性,分別為對映的列名和未被對映的列名,方法如下:

private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {
    List<String> mappedColumnNames = new ArrayList<>();
    List<String> unmappedColumnNames = new ArrayList<>();
    // <1> 獲取配置的列名的字首,全部大寫
    final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);
    /*
     * <2> 獲取 ResultMap 中配置的所有列名,並新增字首
     * 如果在 <select /> 上面配置的是 resultType 屬性,則返回的是空集合,因為它生成的 ResultMap 只有 Java Type 屬性
     */
    final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);
    /*
     * <3> 遍歷資料庫查詢結果中所有的列名
     * 將所有列名分為兩類:是否配置了對映
     */
    for (String columnName : columnNames) {
      final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);
      if (mappedColumns.contains(upperColumnName)) {
        mappedColumnNames.add(upperColumnName);
      } else {
        unmappedColumnNames.add(columnName);
      }
    }
    // <4> 將上面兩類的列名儲存
    mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
    unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}
  1. 獲取配置的列名的字首,全部大寫,通常是沒有配置的

  2. 獲取ResultMap中配置的所有列名,並新增字首

    如果在<select />上面配置的是resultType屬性,則返回的是空集合,因為它建立的ResultMap物件中只有Java Type屬性

  3. 遍歷結果集中所有的列名,如果在<resultMap />標籤中的子標籤配置的column屬性有包含這個列名,則屬於對映的列名

  4. 否則就屬於未被對映的列名

ResultSetHandler

org.apache.ibatis.executor.resultset.ResultSetHandler:結果集對映介面,程式碼如下:

public interface ResultSetHandler {
  /**
   * 處理 {@link java.sql.ResultSet} 成對映的對應的結果
   *
   * @param stmt Statement 物件
   * @param <E>  泛型
   * @return 結果陣列
   * @throws SQLException SQL異常
   */
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  /**
   * 處理 {@link java.sql.ResultSet} 成 Cursor 物件
   *
   * @param stmt Statement 物件
   * @param <E>  泛型
   * @return Cursor 物件
   * @throws SQLException SQL異常
   */
  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  /**
   * 暫時忽略,和儲存過程相關
   *
   * @param cs CallableStatement 物件
   * @throws SQLException SQL異常
   */
  void handleOutputParameters(CallableStatement cs) throws SQLException;
}

DefaultResultSetHandler

org.apache.ibatis.executor.resultset.DefaultResultSetHandler:實現ResultSetHandler介面,處理資料庫的查詢結果,對結果集進行對映,將結果轉換成Java物件

由於該類巢狀的方法太多了,可能一個方法會有十幾層的巢狀,所以本分不會進行全面的分析

因為我檢視這個類的時候是從下面的方法一層一層往上看的,註釋我全部新增了,所以可以參考我的註釋一步一步檢視

接下來的描述可能有點混亂,請按照我在方法前面表明的順序進行檢視,參考:DefaultResultSetHandler.java

先來看下DefaultResultSetHandler處理結果集的方法的流程圖:

DefaultResultHandler-handleResultSet

構造方法

public class DefaultResultSetHandler implements ResultSetHandler {
    /**
     * 延遲載入預設物件
     */
    private static final Object DEFERRED = new Object();
    /**
     * 執行器
     */
    private final Executor executor;
    /**
     * 全域性配置物件
     */
    private final Configuration configuration;
    /**
    * 本次查詢操作對應的 MappedStatement 物件
    */
    private final MappedStatement mappedStatement;
    /**
     * 分頁物件
     */
    private final RowBounds rowBounds;
    /**
     * 引數處理器,預設為 DefaultParameterHandler
     */
    private final ParameterHandler parameterHandler;
    /**
     * 結果處理器,預設為 DefaultResultHandler
     */
    private final ResultHandler<?> resultHandler;
    /**
     * SQL 相關資訊
     */
    private final BoundSql boundSql;
    /**
     * 型別處理器登錄檔
     */
    private final TypeHandlerRegistry typeHandlerRegistry;
    /**
     * 物件例項工廠
     */
    private final ObjectFactory objectFactory;
    /**
     * Reflector 工廠
     */
    private final ReflectorFactory reflectorFactory;

	// nested resultmaps
	private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
	private final Map<String, Object> ancestorObjects = new HashMap<>();
	private Object previousRowValue;

	// multiple resultsets
	private final Map<String, ResultMapping> nextResultMaps = new HashMap<>();
	private final Map<CacheKey, List<PendingRelation>> pendingRelations = new HashMap<>();

	// Cached Automappings
	private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();

	// temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
	private boolean useConstructorMappings;

	public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement,
			ParameterHandler parameterHandler, ResultHandler<?> resultHandler, BoundSql boundSql, RowBounds rowBounds) {
		this.executor = executor;
		this.configuration = mappedStatement.getConfiguration();
		this.mappedStatement = mappedStatement;
		this.rowBounds = rowBounds;
		this.parameterHandler = parameterHandler;
		this.boundSql = boundSql;
		this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
		this.objectFactory = configuration.getObjectFactory();
		this.reflectorFactory = configuration.getReflectorFactory();
		this.resultHandler = resultHandler;
	}
}
  • 上面的屬性有點多,可以先根據註釋進行理解,也可以在接下來的方法中逐步理解

1.handleResultSets方法

handleResultSets(Statement stmt)方法,處理結果集的入口

/**
 * 1.處理結果集
 */
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    /*
     * <1> 用於儲存對映結果集得到的結果隊形
     * 多 ResultSet 的結果集合,每個 ResultSet 對應一個 Object 物件,而實際上,每個 Object 是 List<Object> 物件
     */
    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    // <2> 獲取 ResultSet 物件,並封裝成 ResultSetWrapper
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    /*
     * <3> 獲得當前 MappedStatement 物件中的 ResultMap 集合,XML 對映檔案中 <resultMap /> 標籤生成的
     * 或者 配置 "resultType" 屬性也會生成對應的 ResultMap 物件
     * 在 <select /> 標籤配置 ResultMap 屬性時,可以以逗號分隔配置多個,如果返回多個 ResultSet 則會一一對映,通常配置一個
     */
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // <4> 如果有返回結果,但是沒有 ResultMap 接收物件則丟擲異常
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
        ResultMap resultMap = resultMaps.get(resultSetCount);
        /*
         * <5> 完成結果集的對映,全部轉換的 Java 物件
         * 儲存至 multipleResults 集合中,或者 this.resultHandler 中
         */
        handleResultSet(rsw, resultMap, multipleResults, null);
  		// 獲取下一個結果集
        rsw = getNextResultSet(stmt);
  		// 清空 nestedResultObjects 集合
        cleanUpAfterHandlingResultSet();
  		// 遞增 resultSetCount 結果集數量
        resultSetCount++;
    }

	// <6> 獲取 resultSets 多結果集屬性的配置,儲存過程中使用,暫時忽略
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
        while (rsw != null && resultSetCount < resultSets.length) {
            // 根據 resultSet 的名稱,獲取未處理的 ResultMapping
            ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                // 未處理的 ResultMap 物件
                ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                // 完成結果集的對映,全部轉換的 Java 物件
                handleResultSet(rsw, resultMap, null, parentMapping);
            }
    		// 獲取下一個結果集
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }
    }

    // <7> 如果是 multipleResults 單元素,則取首元素返回
    return collapseSingleResultList(multipleResults);
}
  1. multipleResults用於儲存對映結果集得到的結果隊形,多 ResultSet 的結果集合,每個 ResultSet 對應一個 Object 物件,而實際上,每個 Object 是 List<Object> 物件

  2. 獲取 ResultSet 物件,並封裝成 ResultSetWrapper

  3. 獲得當前 MappedStatement 物件中的 ResultMap 集合,XML 對映檔案中<resultMap />標籤生成的,或者 配置 "resultType" 屬性也會生成對應的 ResultMap 物件

    <select /> 標籤配置 ResultMap 屬性時,可以以逗號分隔配置多個,如果返回多個 ResultSet 則會一一對映,通常配置一個

  4. 如果有返回結果,但是沒有 ResultMap 接收物件則丟擲異常

  5. 呼叫handleResultSet方法,完成結果集的對映,全部轉換的 Java 物件,儲存至 multipleResults 集合中,或者 this.resultHandler 中(使用者自定的,通常不會)

  6. 獲取 resultSets 多結果集屬性的配置,儲存過程中使用,暫時忽略,本文暫不分析

完成結果集對映的任務還是交給了2.handleResultSet方法

2.handleResultSet方法

handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping)方法,處理結果集

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
                             ResultMapping parentMapping) throws SQLException {
    try {
        if (parentMapping != null) {
            // <1> 暫時忽略,因為只有儲存過程的情況時 parentMapping 為非空
            handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
        } else {
            if (resultHandler == null) { // <2>
                // <2.1> 建立 DefaultResultHandler 預設結果處理器
                DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
     			// <2.2> 處理結果集,進行一系列的處理,完成對映,將結果儲存至 DefaultResultHandler 中
                handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
                // <2.3> 將結果集合新增至 multipleResults 中
                multipleResults.add(defaultResultHandler.getResultList());
            } else { // 使用者自定義了 resultHandler,則結果都會儲存在其中
      			// <3> 處理結果集,進行一系列的處理,完成對映,將結果儲存至 DefaultResultHandler 中
                handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
            }
        }
    } finally {
        // issue #228 (close resultsets)
  		// <4> 關閉結果集
        closeResultSet(rsw.getResultSet());
    }
}
  1. 暫時忽略,因為只有儲存過程的情況時 parentMapping 為非空,檢視上面的1.handleResultSets方法的第6

  2. 使用者沒有指定ResultHandler結果處理器

    1. 建立DefaultResultHandler預設結果處理器,就是使用一個List集合儲存轉換後的Java物件
    2. 呼叫handleRowValues方法,處理結果集,進行一系列的處理,完成對映,將結果儲存至 DefaultResultHandler 中
    3. 將結果集合新增至 multipleResults
  3. 使用者指定了自定義的ResultHandler結果處理器,和第2步的區別在於,處理後的Java物件不會儲存在multipleResults 中,僅儲存在ResultHandler中,使用者可通過它獲取

  4. 關閉 ResultSet 結果集物件

通常我們不會自定義結果處理器的,所以第4步本文暫不分析,我們來看到第2步,最終還是交給了3.handleRowValues方法

3.handleRowValues方法

handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)方法,處理結果集

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
        RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    /*
     * <1> ResultMap 存在內嵌的 ResultMap
     * 例如 <resultMap /> 標籤中 <association /> 或者 <collection /> 都會建立對應的 ResultMap 物件
     * 該物件的 id 會設定到 ResultMapping 的 nestedResultMapId 屬性中,這就屬於內嵌的 ResultMap
     */
    if (resultMap.hasNestedResultMaps()) { // 存在
      	// <1.1> 如果不允許在巢狀語句中使用分頁,則對 rowBounds 進行校驗,設定了 limit 或者 offset 則丟擲異常,預設允許
        ensureNoRowBounds();
        // <1.2> 校驗要不要使用自定義的 ResultHandler,針對內嵌的 ResultMap
        checkResultHandler();
  		// <1.3> 處理結果集,進行對映,生成返回結果,儲存至 resultHandler 或者設定到 parentMapping 的對應屬性中
  		// 這裡會處理內嵌的 ResultMap
        handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      	// <2> 處理結果集,進行對映,生成返回結果,儲存至 resultHandler 或者設定到 parentMapping 的對應屬性中
        handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
}
  1. 如果當前 ResultMap 存在內嵌的 ResultMap

    例如 <resultMap /> 標籤中 <association /> 或者 <collection /> 都會建立對應的 ResultMap 物件,該物件的 id 會設定到 ResultMappingnestedResultMapId 屬性中,這就屬於內嵌的 ResultMap

    1. 如果不允許在巢狀語句中使用分頁,則對 rowBounds 進行校驗,設定了 limit 或者 offset 則丟擲異常,預設允許
    2. 校驗要不要使用自定義的 ResultHandler,針對內嵌的 ResultMap
    3. 處理結果集,進行對映,生成返回結果,儲存至 resultHandler 或者設定到 parentMapping(儲存過程相關,本文暫不分析)的對應屬性中,這裡會對內嵌的 ResultMap 進行處理,呼叫handleRowValuesForNestedResultMap方法
  2. 處理結果集,進行對映,生成返回結果,儲存至 resultHandler 或者設定到 parentMapping(儲存過程相關,本文暫不分析)的對應屬性中,呼叫handleRowValuesForSimpleResultMap方法

這裡先來看到第2步中的4.handleRowValuesForSimpleResultMap方法,因為這個處理的情況相比第1步呼叫的方法簡單些

4.handleRowValuesForSimpleResultMap方法

handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)方法,處理結果集(不含巢狀對映)

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, 
                                               ResultHandler<?> resultHandler, RowBounds rowBounds, 
                                               ResultMapping parentMapping) throws SQLException {
    // 預設的上下文物件,臨時儲存每一行的結果且記錄返回結果數量
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    // <1> 根據 RowBounds 中的 offset 跳到到指定的記錄
    skipRows(resultSet, rowBounds);
    // <2> 檢測已經處理的行數是否已經達到上限(RowBounds.limit)以及 ResultSet 中是否還有可處理的記錄
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      /*
       * <3> 獲取最終的 ResultMap
       * 因為 ResultMap 可能使用到了 <discriminator /> 標籤,需要根據不同的值對映不同的 ResultMap
       * 如果存在 Discriminator 鑑別器,則根據當前記錄選擇對應的 ResultMap,會一直巢狀處理
       */
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      // <4> 從結果集中獲取到返回結果物件,進行對映,比較複雜,關鍵方法!!!
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      // <5> 將返回結果物件儲存至 resultHandler,或者設定到父物件 parentMapping 的對應屬性中
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
}

這裡建立了一個DefaultResultContext儲存結果的上下文物件,點選去你會發現有3個屬性:

  • resultObject:暫存對映後的返回結果,因為結果集中可能有很多條資料
  • resultCount:記錄經過 DefaultResultContext 暫存的物件個數
  • stopped:控制是否還進行對映
  1. 根據 RowBounds 中的 offset 跳到到結果集中指定的記錄

  2. 檢測已經處理的行數是否已經達到上限(RowBounds.limit)以及 ResultSet 中是否還有可處理的記錄

  3. 呼叫resolveDiscriminatedResultMap方法,獲取最終的 ResultMap

    因為 ResultMap 可能使用到了 <discriminator /> 標籤,需要根據不同的值對映不同的 ResultMap
    如果存在 Discriminator 鑑別器,則根據當前記錄選擇對應的 ResultMap,會一直巢狀處理

  4. 呼叫getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)方法,從結果集中獲取到返回結果物件,進行對映,比較複雜,關鍵方法!!!

  5. 呼叫storeObject方法,將返回結果物件儲存至 resultHandler,或者設定到父物件 parentMapping(儲存過程相關,本文暫不分析)的對應屬性中

對於第345步的三個方法,我們一個一個來看

  • 4.1resolveDiscriminatedResultMap方法

  • 4.2getRowValue方法

  • 4.3storeObject方法

4.1resolveDiscriminatedResultMap方法

resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)方法,如果存在<discriminator />鑑別器,則進行處理,選擇對應的 ResultMap,會一直巢狀處理

public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
        throws SQLException {
    // 記錄已經處理過的 ResultMap 的 id
    Set<String> pastDiscriminators = new HashSet<>();
    // <1> 獲取 ResultMap 中的 Discriminator 鑑別器,<discriminator />標籤會被解析成該物件
    Discriminator discriminator = resultMap.getDiscriminator();
    while (discriminator != null) {
        // <2> 獲取當前記錄中該列的值,通過型別處理器轉換成了對應的型別
        final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
        // <3> 鑑別器根據該值獲取到對應的 ResultMap 的 id
        final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
        if (configuration.hasResultMap(discriminatedMapId)) {
            // <3.1> 獲取到對應的 ResultMap
            resultMap = configuration.getResultMap(discriminatedMapId);
            // <3.2> 記錄上一次的鑑別器
            Discriminator lastDiscriminator = discriminator;
            // <3.3> 獲取到對應 ResultMap 內的鑑別器,可能鑑別器裡面還有鑑別器
            discriminator = resultMap.getDiscriminator();
            // <3.4> 檢測是否出現迴圈巢狀了
            if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
                break;
            }
        } else {
          	// <4> 鑑別結果沒有對應的 ResultMap,則直接跳過
            break;
        }
    }
    // <5> 返回最終使用的 ResultMap 物件
    return resultMap;
}
  1. 獲取 ResultMap 中的 Discriminator 鑑別器,<discriminator /> 標籤會被解析成該物件

  2. 呼叫getDiscriminatorValue方法,獲取當前記錄中該列的值,通過型別處理器轉換成了對應的型別,方法如下:

    private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException {
        // 獲取 <discriminator />標籤對應的的 ResultMapping 物件
        final ResultMapping resultMapping = discriminator.getResultMapping();
        // 獲取 TypeHandler 型別處理器
        final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
        // 通過 TypeHandler 從 ResultSet 中獲取該列的值
        return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
    }
    
  3. Discriminator 鑑別器根據該值獲取到對應的 ResultMap 的 id

    1. 存在對應的 ResultMap 物件,則獲取到
    2. 記錄上一次的鑑別器
    3. 獲取到對應 ResultMap 內的鑑別器,可能鑑別器裡面還有鑑別器
    4. 檢測是否出現迴圈巢狀了
  4. Discriminator 鑑別結果沒有對應的 ResultMap,則直接跳過

  5. 返回最終使用的 ResultMap 物件

4.2getRowValue方法

getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)方法,處理結果集

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    // <1> 儲存延遲載入的集合
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // <2> 建立返回結果的例項物件(如果存在巢狀子查詢且是延遲載入則為其建立代理物件,後續的延遲載入儲存至 lazyLoader 中即可)
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);

    /*
     * <3> 如果上面建立的返回結果的例項物件不為 null,並且沒有對應的 TypeHandler 型別處理器,則需要對它進行賦值
     * 例如我們返回結果為 java.lang.String 就不用了,因為上面已經處理且賦值了
     */
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        // <3.1> 將返回結果的例項物件封裝成 MetaObject,便於操作
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
  		// <3.2> 標記是否成功對映了任意一個屬性,useConstructorMappings 表示是否在構造方法中使用了引數對映
        boolean foundValues = this.useConstructorMappings;
        // <3.3> 檢測是否需要自動對映
        if (shouldApplyAutomaticMappings(resultMap, false)) {
            /*
             * <3.4> 從結果集中將未被對映的列值設定到返回結果 metaObject 中
             * 返回是否對映成功,設定了1個或以上的屬性值
             */
            foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        /*
         * <3.5> 從結果集中將 ResultMap 中需要對映的列值設定到返回結果 metaObject 中
         * 返回是否對映成功,設定了1個或以上的屬性值
         */
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        foundValues = lazyLoader.size() > 0 || foundValues;
        /*
         * <3.6> 如果沒有成功對映任意一個屬性,則根據 returnInstanceForEmptyRow 全域性配置(預設為false)返回空物件還是 null
         */
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    // <4> 返回該結果物件
    return rowValue;
}
  1. 建立一個儲存延遲載入的集合ResultLoaderMap物件lazyLoader,如果存在代理物件,建立的代理物件則需要通過它來執行需要延遲載入的方法,在後續會將到?

  2. 呼叫createResultObject方法,建立返回結果的例項物件rowValue(如果存在巢狀子查詢且是延遲載入則為其建立代理物件,後續的延遲載入儲存至 lazyLoader 中即可)

  3. 如果上面建立的返回結果的例項物件rowValue不為 null,並且沒有對應的 TypeHandler 型別處理器,則需要對它進行賦值

    例如我們返回結果為 java.lang.String 就不用了,因為上面已經處理且賦值了

    1. 將返回結果的例項物件封裝成 MetaObject 物件metaObject,便於操作
    2. 標記是否成功對映了任意一個屬性,useConstructorMappings 表示是否在構造方法中使用了引數對映
    3. 呼叫shouldApplyAutomaticMappings方法,檢測是否需要自動對映,就是對未被對映的列進行處理
    4. 呼叫applyAutomaticMappings方法,從結果集中將未被對映的列值設定到返回結果 metaObject 中,返回是否對映成功(設定了1個或以上的屬性值)
    5. 呼叫applyPropertyMappings方法,從結果集中將 ResultMap 中需要對映的列值設定到返回結果 metaObject 中,返回是否對映成功(設定了1個或以上的屬性值)
    6. 如果沒有成功對映任意一個屬性,則根據 returnInstanceForEmptyRow 全域性配置(預設為false)返回空物件還是 null
  4. 返回該結果物件rowValue

我們逐步來看上面的第23.33.43.5所呼叫的方法

  • 4.2.1createResultObject方法
  • 4.2.2shouldApplyAutomaticMappings方法
  • 4.2.3applyAutomaticMappings方法
  • 4.2.4applyPropertyMappings方法

4.2.1createResultObject方法

createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix)方法,建立返回結果的例項物件(如果存在巢狀子查詢且是延遲載入則為其建立代理物件)

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
        String columnPrefix) throws SQLException {
  	// 標記構造方法中是否使用了引數對映
    this.useConstructorMappings = false; // reset previous mapping result
    // <1> 記錄構造方法的入參型別
    final List<Class<?>> constructorArgTypes = new ArrayList<>();
    // <2> 記錄構造方法的引數值
    final List<Object> constructorArgs = new ArrayList<>();
    // <3> 建立返回結果的例項物件,該步驟的核心!!!
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    /*
     * <4> 如果返回結果的例項物件不為空,且返回結果沒有對應的 TypeHandler 型別處理器
     * 則遍歷所有的對映列,如果存在巢狀子查詢並且要求延遲載入,那麼為該返回結果的例項物件建立一個動態代理物件(Javassist)
     * 這樣一來可以後續將需要延遲載入的屬性放入 `lazyLoader` 中即可
     *
     * 為該物件建立對應的代理物件,其中通過 ResultLoaderMap 對延遲載入的方法進行了增強
     * 呼叫 getter 方法時執行查詢並從 ResultLoaderMap 中刪除,直接呼叫 setter 方法也會從中刪除
     */
    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;
            }
        }
    }
    // <5> 記錄是否使用有參構造方法建立的該返回結果例項物件
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
    return resultObject;
}
  1. 記錄構造方法的入參型別

  2. 記錄構造方法的引數值

  3. 呼叫createResultObject方法(過載),建立返回結果的例項物件,該步驟的核心!!!

  4. 如果返回結果的例項物件不為空,且返回結果沒有對應的 TypeHandler 型別處理器,例如一個實體類,則遍歷所有的對映列,如果存在巢狀子查詢並且要求延遲載入,那麼為該返回結果的例項物件建立一個動態代理物件Javassist

    這樣一來可以後續將需要延遲載入的屬性放入 lazyLoader 中即可,在後續會講到?

  5. 記錄是否使用有參構造方法建立的該返回結果例項物件,就是使用了對映,後續判斷返回空物件還是null需要用到

  6. 返回例項物件,也可能是它的動態代理物件

這裡我們需要來看到第3步呼叫的createResultObject過載方法

  • 4.2.1.1createResultObject過載方法
4.2.1.1createResultObject過載方法

createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)方法,找到構造方法,建立一個例項物件

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
        List<Object> constructorArgs, String columnPrefix) throws SQLException {
    // 獲取 Java Type
    final Class<?> resultType = resultMap.getType();
    // 建立對應的 MetaClass 物件,便於操作
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
    // 獲取 <constructor /> 標籤下建構函式的入參資訊,可以通過這些入參確認一個建構函式
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();

    /*
     * 建立結果物件,分為下面4種場景:
     * 1. 結果集只有一列,且存在對應的 TypeHandler 型別處理器,例如返回 java.lang.String
     * 2. <resultMap /> 標籤下配置的 <constructor /> 標籤下的建構函式引數資訊不為空
     * 3. 返回型別為介面,或者有預設的構造方法
     * 4. 找到合適的構造方法
     */
    if (hasTypeHandlerForResultObject(rsw, resultType)) { // 場景1
        // 將該列轉換成對應 Java Type 的值,然後返回
        return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    } else if (!constructorMappings.isEmpty()) { // 場景2
        // 根據 <constructor /> 標籤下的構造方法入參配置,嘗試從結果集中獲取入參值,並建立返回結果的例項物件
        return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) { // 場景3
      	// 使用預設無參構造方法建立返回結果的例項物件
        return objectFactory.create(resultType);
    } else if (shouldApplyAutomaticMappings(resultMap, false)) { // 場景4
        // 找到合適的構造方法並建立返回結果物件
        return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
    }
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

建立結果物件,依次分為下面4種場景:

  1. 結果集只有一列,且存在對應的 TypeHandler 型別處理器,例如返回 java.lang.String

    則呼叫createPrimitiveResultObject方法,將該列轉換成對應 Java Type 的值,然後返回

  2. <resultMap /> 標籤下配置的 <constructor /> 標籤下的建構函式引數資訊不為空

    則呼叫createParameterizedResultObject方法,根據 <constructor / 標籤下的構造方法入參配置,嘗試從結果集中獲取入參值,並建立返回結果的例項物件

  3. 返回型別為介面,或者有預設的構造方法

    則通過例項工廠objectFactory,使用預設無參構造方法建立返回結果的例項物件

  4. 找到合適的構造方法

    則呼叫createByConstructorSignature方法,找到合適的構造方法並建立返回結果物件

好的,接下來我們又要看到第124步呼叫的三個方法了

  • 4.2.1.2createPrimitiveResultObject方法
  • 4.2.1.3createParameterizedResultObject方法
  • 4.2.1.4createByConstructorSignature方法
4.2.1.2createPrimitiveResultObject方法

createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)方法,建立返回結果例項物件(通常是Java定義的型別,例如java.lang.String)

private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
        throws SQLException {
    // 獲取 Java Type
    final Class<?> resultType = resultMap.getType();
    final String columnName;
    /*
     * 獲取列名
     */
    if (!resultMap.getResultMappings().isEmpty()) { // 配置了 <resultMap />
      // 獲取 <resultMap /> 標籤下的配置資訊
        final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
        // 因為只有一個引數,則直接取第一個
        final ResultMapping mapping = resultMappingList.get(0);
        // 從配置中獲取 column 屬性
        columnName = prependPrefix(mapping.getColumn(), columnPrefix);
    } else {
      // 從結果集中獲取列名
        columnName = rsw.getColumnNames().get(0);
    }
    // 通過 Java Type 和列名獲取對應的 TypeHandler
    final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
    // 通過 TypeHandler 將返回結果轉換成對應 Java Type 的值
    return typeHandler.getResult(rsw.getResultSet(), columnName);
}
  1. 通過ResultSetWrapper根據Java Type和columnName找到對應的TypeHandler型別處理器
  2. 通過TypeHandler型別處理器,將結果集中的結果轉換成對應的 Java 物件
4.2.1.3createParameterizedResultObject方法

createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)方法

根據 <resultMap /> 標籤下的 <constructor /> 標籤配置的引數構建一個例項物件

Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType,
        List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
        String columnPrefix) {
    // 標記是否找到配置的建構函式的所有入參
    boolean foundValues = false;
    for (ResultMapping constructorMapping : constructorMappings) {
        // 獲取引數的 Java Type
        final Class<?> parameterType = constructorMapping.getJavaType();
        // 獲取引數對應的 column 列名
        final String column = constructorMapping.getColumn();
        final Object value;
        try {
            /*
             * 獲取該屬性值,可能存在以下幾種場景:
             * 1. 存在巢狀查詢
             * 2. 存在巢狀 ResultMap
             * 3. 直接獲取值
             */
            if (constructorMapping.getNestedQueryId() != null) { // 場景1
              	// 通過巢狀查詢獲取到該屬性值
                value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
            } else if (constructorMapping.getNestedResultMapId() != null) { // 場景2
              	// 獲取到巢狀的 ResultMap 物件
                final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
                // 從結果集中獲取到巢狀 ResultMap 對應的值
                value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping));
            } else { // 場景3
                final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
                // 通過 TypeHandler 從結果集中獲取該列的值
                value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
            }
        } catch (ResultMapException | SQLException e) {
            throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
        }
        constructorArgTypes.add(parameterType);
        constructorArgs.add(value);
        foundValues = value != null || foundValues;
    }
    // 如果建構函式的入參全部找到,則建立返回結果的例項物件
    return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
}
  1. 需要先從結果集中獲取每個<constructor /> 標籤配置的引數對應的值,這裡又可能存在以下三種情況:

    1. 該引數存在巢狀查詢,則呼叫getNestedQueryConstructorValue方法,獲取到該屬性值
    2. 存在巢狀 ResultMap,則呼叫getRowValue方法,從該結果集中獲取到巢狀 ResultMap 對應的值,回到了4.2getRowValue方法
    3. 正常情況,通過TypeHandler型別處理器,根據列名從結果集中獲取到該屬性值
  2. 通過objectFactory例項工廠,根據上面配置的入參資訊構建一個例項物件

這裡我們又要進入第1.1步的方法

  • 4.2.1.3.1getNestedQueryConstructorValue方法
4.2.1.3.1getNestedQueryConstructorValue方法

getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)方法,處理構造方法的入參出現巢狀子查詢這種情況,獲取該引數值

private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)
        throws SQLException {
    // <1> 獲得巢狀查詢關聯的 id
    final String nestedQueryId = constructorMapping.getNestedQueryId();
    // <2> 獲取巢狀查詢對應的 MappedStatement 物件
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
    // <3> 獲取巢狀查詢的引數型別
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
    // <4> 獲取巢狀查詢的引數物件,已完成初始化
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, nestedQueryParameterType, columnPrefix);
    Object value = null;
    // <5> 執行查詢
    if (nestedQueryParameterObject != null) {
        // <5.1> 獲取巢狀查詢中的 SQL 物件
        final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
        // <5.2> 獲取CacheKey物件
        final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
        final Class<?> targetType = constructorMapping.getJavaType();
        // <5.3> 建立 ResultLoader 物件
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, 
                                                           nestedQueryParameterObject, targetType, key, nestedBoundSql);
        // <5.4> 載入結果
        value = resultLoader.loadResult();
    }
    return value;
}
  1. 獲得巢狀查詢關聯的 id

  2. 獲取巢狀查詢對應的 MappedStatement 物件

  3. 獲取巢狀查詢的引數型別

  4. 獲取巢狀查詢的引數物件,已完成初始化,呼叫prepareParameterForNestedQuery方法,進去後發現又得兩層方法?,這裡就不再展開了,比較簡單,可以先參考的我的註釋檢視,在後續還會呼叫該方法,再進行解析

  5. 執行查詢,因為這裡的構造方法中的入參,所以無需判斷延遲載入,在後面設定屬性時就不一樣了

    1. 獲取巢狀查詢中的 SQL 物件
    2. 獲取CacheKey物件
    3. 建立 ResultLoader 物件
    4. 載入結果
  6. 返回子查詢返回的值

4.2.1.4createByConstructorSignature方法

createByConstructorSignature(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)方法,嘗試找一個合適的構造方法構建一個例項物件

private Object createByConstructorSignature(ResultSetWrapper rsw, Class<?> resultType,
        List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
    // <1> 獲取所有的建構函式
    final Constructor<?>[] constructors = resultType.getDeclaredConstructors();
    // <2> 找到新增了 @AutomapConstructor 註解的構造方法
    final Constructor<?> defaultConstructor = findDefaultConstructor(constructors);
    if (defaultConstructor != null) {
        // 使用這個構造方法建立返回結果的例項物件
        return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, defaultConstructor);
    } else {
        for (Constructor<?> constructor : constructors) { // <3> 遍歷所有的構造方法
            // 如果構造方法的入參與結果集中列的個數相同,並且入參的 Java Type 和列的 Jdbc Type 有型別處理器
            if (allowedConstructorUsingTypeHandlers(constructor, rsw.getJdbcTypes())) {
                // 使用這個構造方法建立返回結果的例項物件
                return createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, constructor);
            }
        }
    }
    throw new ExecutorException("No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames());
}
  1. 獲取所有的建構函式
  2. 找到新增了 @AutomapConstructor 註解的構造方法,如果存在則呼叫createUsingConstructor方法,建立一個例項物件
  3. 否則,遍歷所有的構造方法
    1. 如果構造方法的入參與結果集中列的個數相同,並且入參的 Java Type 和列的 Jdbc Type 有型別處理器
    2. 使用這個構造方法建立返回結果的例項物件,呼叫createUsingConstructor方法,建立一個例項物件

上面需要呼叫的createUsingConstructor方法比較簡單,這裡就不再展開了,大致邏輯就是從結果集中獲取到該構造方法所有的入參,然後構建一個例項物件

4.2.2shouldApplyAutomaticMappings方法

shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested)方法,檢測是否需要自動對映(對未被對映的列進行處理)

private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
    /*
     * 獲取<resultMap />中的 autoMapping 配置
     * 如果不為空則返回該值,是否自定對映
     */
    if (resultMap.getAutoMapping() != null) {
        return resultMap.getAutoMapping();
    } else {
        /*
         * 全域性配置 AutoMappingBehavior 預設為 PARTIAL
         * 如果是巢狀,這裡預設就返回 false
         */
        if (isNested) { // 巢狀對映
            return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
        } else {
            return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
        }
    }
}
  1. 如果<resultMap />中的autoMapping配置不為空,則返回該配置
  2. 否則通過全域性配置來判斷,預設PARTIAL,也就是不是巢狀對映則需要對未被對映的列進行處理,巢狀查詢的話不會對未被對映的列進行處理(需要配置為FULL

4.2.3applyAutomaticMappings方法

applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix)方法,對未被對映的欄位進行對映

private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
        String columnPrefix) throws SQLException {
	// <1> 將這些未被對映的欄位建立對應的 UnMappedColumnAutoMapping 物件
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
	// 標記是否找到1個以上的屬性值,延遲載入也算
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      // <2> 遍歷未被對映的欄位陣列,將這些屬性設定到返回結果物件中
        for (UnMappedColumnAutoMapping mapping : autoMapping) {
            // <2.1> 通過 TypeHandler 獲取未被對映的欄位的值
            final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
            if (value != null) {
                foundValues = true;
            }
            /*
             * <2.2> 如果屬性值不為空,或者配置了值為 null 也往返回結果設定該屬性值(不能是基本型別)
             */
            if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
                // gcode issue #377, call setter on nulls (value is not 'found')
      			// 往返回結果設定屬性值
                metaObject.setValue(mapping.property, value);
            }
        }
    }
    return foundValues;
}
  1. 呼叫createAutomaticMappings方法,將這些未被對映的欄位建立對應的 UnMappedColumnAutoMapping 物件(包含列名、屬性名、型別處理器、是否為原始型別)
  2. 遍歷未被對映的欄位陣列,將這些屬性設定到返回結果物件中
    1. 通過 TypeHandler 型別處理器獲取未被對映的欄位的值
    2. 如果屬性值不為空,或者配置了值為 null 也往返回結果設定該屬性值(不能是基本型別),則往返回結果中設定該屬性值

這裡我們來看到createAutomaticMappings方法

  • 4.2.3.1createAutomaticMappings方法
4.2.3.1createAutomaticMappings方法

createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix)方法,將這些未被對映的欄位建立對應的 UnMappedColumnAutoMapping 物件

private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap,
        MetaObject metaObject, String columnPrefix) throws SQLException {
  	// <1> ResultMap 中需要 "自動對映" 的列會快取起來,這是對應的快取 key
    final String mapKey = resultMap.getId() + ":" + columnPrefix;
    // <2> 先從快取中獲取
    List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
    if (autoMapping == null) {
        autoMapping = new ArrayList<>();
        // <3> 獲取未對映的的列名集合,也就是資料庫返回的列名在 ResultMap 中沒有配置,例如我們配置的是 resultType 屬性就全部沒有配置
        final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
        for (String columnName : unmappedColumnNames) {
            String propertyName = columnName;
            /*
             * <4> 如果配置了字首,則將列名中的字首去掉作為屬性名
             */
            if (columnPrefix != null && !columnPrefix.isEmpty()) {
                // When columnPrefix is specified, ignore columns without the prefix.
                if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
                    // 如果列名以字首開頭則將字首去除
                    propertyName = columnName.substring(columnPrefix.length());
                } else {
                    continue;
                }
            }
            /**
             * <5> 根據列名從入參物件中獲取對應的屬性名稱,不管大小寫都可以找到
             * {@link org.apache.ibatis.reflection.Reflector#caseInsensitivePropertyMap)
             */
            final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
            // <6> 開始建立 UnMappedColumnAutoMapping 物件
            if (property != null && metaObject.hasSetter(property)) {
                if (resultMap.getMappedProperties().contains(property)) {
        			// 如果該屬性配置了對映關係則跳過
                    continue;
                }
                // <6.1> 獲取屬性名稱的 Class 物件
                final Class<?> propertyType = metaObject.getSetterType(property);
                if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
                    final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
                    // <6.2.1> 建立該屬性的 UnMappedColumnAutoMapping 物件,設定列名、屬性名、型別處理器、是否為原始型別
                    autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
                } else {
        			// <6.2.2> 執行發現自動對映目標為未知列(或未知屬性型別)的行為,預設為 NONE,不做任何行為
                    configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property, propertyType);
                }
            } else {
                // 執行發現自動對映目標為未知列(或未知屬性型別)的行為,預設為 NONE,不做任何行為
                configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName,
                        (property != null) ? property : propertyName, null);
            }
        }
        autoMappingsCache.put(mapKey, autoMapping);
    }
    return autoMapping;
}
  1. ResultMap 中需要 "自動對映" 的列會快取起來,這是對應的快取 key

  2. 先從autoMappingsCache快取中獲取該 ResultMap 對應的 UnMappedColumnAutoMapping 集合 autoMapping,沒有的話才進行接下來的解析

  3. 獲取未對映的的列名集合,也就是資料庫返回的列名在 ResultMap 中沒有配置,例如我們配置的是 resultType 屬性就全部沒有配置,然後進行遍歷

  4. 如果配置了字首,則將列名中的字首去掉作為屬性名

  5. 根據列名從入參物件中獲取對應的屬性名稱,不管大小寫都可以找到

  6. 開始為該屬性建立 UnMappedColumnAutoMapping 物件,如果返回物件中有該屬性的 setter 方法

    1. 獲取屬性名稱的 Class 物件
    2. 如果有對應的 TypeHandler 型別處理器,建立該屬性的 UnMappedColumnAutoMapping 物件,設定列名、屬性名、型別處理器、是否為原始型別,新增到autoMapping集合中
    3. 否則,執行發現自動對映目標為未知列(或未知屬性型別)的行為,預設為 NONE,不做任何行為
  7. 該屬性沒有setter方法,執行發現自動對映目標為未知列(或未知屬性型別)的行為,預設為 NONE,不做任何行為

  8. 返回autoMapping,並新增到autoMappingsCache快取中

4.2.4applyPropertyMappings方法

applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)方法,將明確被對映的欄位設定到返回結果中

private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
        ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    // <1> 獲取 ResultMap 中明確需要進行對映的列名集合
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    // 標記是否找到1個以上的屬性值,延遲載入也算
    boolean foundValues = false;
    // <2> 獲取 ResultMap 中所有的 ResultMapping 物件
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      // 獲取欄位名
        String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
        if (propertyMapping.getNestedResultMapId() != null) {
            // the user added a column attribute to a nested result map, ignore it
            column = null;
        }
        /*
         * <3> 從結果集獲取屬性值設定到返回結果中,處理以下三種場景:
         * 1. 配置的 column 屬性為"{prop1:col1,prop2:col2}"這種形式,
         * 一般就是巢狀子查詢,表示將col1和col2的列值設定到巢狀子查詢的入參物件的prop1和prop2屬性中
         * 2. 基本型別的屬性對映
         * 3. 多結果集的場景處理,該屬性來自另一個結果集
         *
         * 對於沒有配置 column 屬性不會處理
         */
        if (propertyMapping.isCompositeResult() // 場景1
                || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) // 場景2
                || propertyMapping.getResultSet() != null) { // 場景3
            // <4> 完成對映,從結果集中獲取到對應的屬性值
            Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
            // issue #541 make property optional
            final String property = propertyMapping.getProperty();
            if (property == null) {
                // <4.1> 沒有配置對應的 Java 屬性則跳過
              continue;
            } else if (value == DEFERRED) {
      			// <4.2> 如果是佔位符,則跳過
                foundValues = true;
                continue;
            }
            if (value != null) {
                foundValues = true;
            }
            if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
                // gcode issue #377, call setter on nulls (value is not 'found')
      			// <4.3> 將屬性值設定到返回結果中
                metaObject.setValue(property, value); // 設定屬性值
            }
        }
    }
    return foundValues;
}
  1. 獲取 ResultMap 中明確需要進行對映的列名集合mappedColumnNames

  2. 獲取 ResultMap 中所有的 ResultMapping 物件,然後進行遍歷

  3. 從結果集獲取屬性值設定到返回結果中,需要滿足下面三個條件中的一個:

    • 配置的 column 屬性為{prop1:col1,prop2:col2}這種形式,一般就是巢狀子查詢,表示將col1和col2的列值設定到巢狀子查詢的入參物件的prop1和prop2屬性中
    • 基本型別的屬性對映
    • 多結果集的場景處理,該屬性來自另一個結果集,儲存過程相關,本文暫不分析
  4. 完成對映,呼叫getPropertyMappingValue方法,從結果集中獲取到對應的屬性值value

    1. 沒有配置對應的 Java 屬性則跳過
    2. 如果是DEFERRED佔位符(延遲載入),則跳過
    3. 將屬性值設定到返回結果中

我們來看看getPropertyMappingValue方法

  • 4.2.4.1getPropertyMappingValue方法
4.2.4.1getPropertyMappingValue方法

getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)方法

完成對映,從結果集中獲取到對應的屬性值

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
        ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    if (propertyMapping.getNestedQueryId() != null) { // 巢狀子查詢
        // <1> 執行巢狀子查詢,返回查詢結果,如果需要延遲記載則返回的是 DEFERRED
        return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if (propertyMapping.getResultSet() != null) { // 多結果集,儲存過程相關,暫時忽略
        // <2> 多結果集處理,延遲載入,返回佔位符
        addPendingChildRelation(rs, metaResultObject, propertyMapping);
        return DEFERRED;
    } else { // 結果對映
        // 獲取 TypeHandler 型別處理器
        final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
        final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
        // <3> 通過 TypeHandler 型別處理器從結果集中獲取該列對應的屬性值
        return typeHandler.getResult(rs, column);
    }
}

可以看到該ResultMapping屬性配置可能有三種情況:

  1. 如果是巢狀子查詢,則呼叫getNestedQueryMappingValue方法,執行巢狀子查詢,返回查詢結果,如果需要延遲記載則返回的是 DEFERRED
  2. 如果是多結果集,儲存過程相關,則呼叫addPendingChildRelation方法, 多結果集處理,延遲載入,返回佔位符,本文暫不分析
  3. 正常情況,獲取 TypeHandler 型別處理器,通過它從結果集中獲取該列對應的屬性值

我們這裡繼續看到第1步中的呼叫的方法

  • 4.2.4.2getNestedQueryMappingValue方法
4.2.4.2getNestedQueryMappingValue方法

getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)方法

執行巢狀子查詢,返回查詢結果,如果需要延遲記載則返回的是 DEFERRED

private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
        ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    // <1> 獲取巢狀子查詢關聯的ID
    final String nestedQueryId = propertyMapping.getNestedQueryId();
    // 獲得屬性名
    final String property = propertyMapping.getProperty();
    // 獲得巢狀子查詢的 MappedStatement 物件
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
    // 獲得巢狀子查詢的引數型別
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
    // <2> 準備好巢狀子查詢的入參
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
            nestedQueryParameterType, columnPrefix);
    Object value = null;
    if (nestedQueryParameterObject != null) {
        // <3> 獲得巢狀子查詢的 BoundSql 物件
        final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
        // <4> 獲得巢狀子查詢本次查詢的 CacheKey 物件
        final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
  		// 巢狀子查詢的返回 Java Type
        final Class<?> targetType = propertyMapping.getJavaType();
        // <5> 檢查快取中已存在
        if (executor.isCached(nestedQuery, key)) {
            // <5.1> 建立 DeferredLoad 物件,並通過該 DeferredLoad 物件從快取中載入結果物件
            executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
            // <5.2> 返回已定義
            value = DEFERRED;
        } else { // <6> 快取中不存在
            // <6.1> 建立 ResultLoader 物件
            final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
                    nestedQueryParameterObject, targetType, key, nestedBoundSql);
            if (propertyMapping.isLazy()) { // <6.2> 如果要求延遲載入,則延遲載入
                // <6.2.1> 如果該屬性配置了延遲載入,則將其新增到 `ResultLoader.loaderMap` 中,等待真正使用時再執行巢狀查詢並得到結果物件
                lazyLoader.addLoader(property, metaResultObject, resultLoader);
                // <6.2.2> 返回延遲載入佔位符
                value = DEFERRED;
            } else { // <6.3> 如果不要求延遲載入,則直接執行載入對應的值
                value = resultLoader.loadResult();
            }
        }
    }
    return value;
}
  1. 獲取巢狀子查詢關聯的ID、屬性名、巢狀子查詢的 MappedStatement 物件、巢狀子查詢的引數型別

  2. 從結果集中獲取引數值,準備好巢狀子查詢的入參,呼叫prepareParameterForNestedQuery方法

  3. 獲得巢狀子查詢的 BoundSql 物件

  4. 獲得巢狀子查詢本次查詢的 CacheKey 物件、巢狀子查詢的返回 Java Type

  5. 檢查快取中已存在本次子查詢的資料,已存在的話則進行下面兩步

    1. 則建立 DeferredLoad 物件,並通過該 DeferredLoad 物件從快取中載入結果物件

      這也算延遲載入,巢狀子查詢的結果在快取中,然後會在查詢介面後進行載入,可以回到《SQL執行過程(一)之Executor》BaseExecutor小節中的query方法的第6步看看

    2. 返回DEFERRED延遲載入預設物件

  6. 快取中不存在本次子查詢的資料

    1. 建立 ResultLoader 物件resultLoader

    2. 如何該屬性還要求了是延遲載入

      1. 則將其新增到ResultLoader.loaderMap 中,等待真正使用時再執行巢狀查詢並得到結果物件

        可以回到4.2.1createResultObject方法的第4步看一下,如果存在巢狀子查詢並且要求延遲載入,那麼為該返回結果的例項物件建立一個動態代理物件Javassist),後續將需要延遲載入的屬性放入 lazyLoader (就是上面的ResultLoader)中即可

      2. 返回DEFERRED延遲載入預設物件

    3. 否在直接載入resultLoader物件,獲取到該屬性值

  7. 最後返回該屬性值或者DEFERRED延遲載入預設物件

這裡我們再來看到第2步中呼叫的方法

  • 4.2.4.2.1prepareParameterForNestedQuery方法
4.2.4.2.1prepareParameterForNestedQuery方法

prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix)方法,為巢狀子查詢準備好入參

private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
        String columnPrefix) throws SQLException {
    if (resultMapping.isCompositeResult()) { // 巢狀子查詢是否有多個屬性對映
      	// 從結果集中獲取多個屬性值設定到入參物件中
        return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
    } else {
      	// 從結果集中直接獲取巢狀查詢的入參
        return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
    }
}
  1. 巢狀子查詢是有多個屬性對映,則呼叫prepareCompositeKeyParameter方法,從結果集中獲取多個屬性值設定到入參物件中

    配置的column屬性為{prop1:col1,prop2:col2}這種形式,表示將col1和col2的列值設定到巢狀子查詢的入參物件的prop1和prop2屬性中

  2. 只有一個屬性對映,則呼叫prepareSimpleKeyParameter方法,從結果集中直接獲取巢狀查詢的入參

來看看第12步呼叫的方法

  • 4.2.4.2.2prepareCompositeKeyParameter方法
  • 4.2.4.2.3prepareSimpleKeyParameter方法
4.2.4.2.2prepareCompositeKeyParameter方法

prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix)方法

處理巢狀子查詢有多個屬性對映作為入參的場景,獲取到多個屬性值到子查詢的入參中

private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
        String columnPrefix) throws SQLException {
  	// 建立一個巢狀子查詢的入參的例項物件
    final Object parameterObject = instantiateParameterObject(parameterType);
    final MetaObject metaObject = configuration.newMetaObject(parameterObject);
    // 標記是否找到一個或以上的屬性值
    boolean foundValues = false;
    for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
      	// 獲取巢狀子查詢的入參該屬性的 Java Type
        final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
        final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
        //通過 TypeHandler 根據該屬性的 column 列名從該結果集中獲取值
        final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
        // issue #353 & #560 do not execute nested query if key is null
        if (propValue != null) {
          	// 設定屬性值到入參物件中
            metaObject.setValue(innerResultMapping.getProperty(), propValue);
            foundValues = true;
        }
    }
    return foundValues ? parameterObject : null;
}
  1. 建立一個巢狀子查詢的入參的例項物件parameterObject,呼叫instantiateParameterObject方法,很簡單,點選去看一下就知道了

  2. 開始遍歷ResultMapping中的List<ResultMapping> composites組合欄位

  3. 獲取巢狀子查詢的入參該屬性對應的 TypeHandler 處理器

  4. 通過 TypeHandler 根據該屬性的 column 列名從該結果集中獲取值

  5. 設定屬性值到子查詢的入參物件中

  6. 返回parameterObject子查詢入參物件

4.2.4.2.3prepareSimpleKeyParameter方法

prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix)方法,從結果集中直接獲取巢狀查詢的入參

private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
        String columnPrefix) throws SQLException {
    final TypeHandler<?> typeHandler;
    if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
        typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
    } else {
        typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
    }
    // 根據型別處理器從結果集中獲取該列的值,作為巢狀查詢的入參
    return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
}
  1. 根據Java Type獲取到 TypeHandler 型別處理器
  2. 根據TypeHandler從結果集中將該列對應的值轉化成入參

4.3storeObject方法

storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs)方法

將返回結果 物件儲存至 resultHandler,或者設定到父物件 parentMapping(儲存過程相關,本文暫不分析)的對應屬性中

private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext,
        Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
    if (parentMapping != null) {
        // 巢狀查詢或巢狀對映,將返回結果設定到父物件的對應屬性中
        linkToParents(rs, parentMapping, rowValue);
    } else {
        // 普通對映,將結果物件儲存到 ResultHandler 中
        callResultHandler(resultHandler, resultContext, rowValue);
    }
}
  1. 如果parentMapping不為空,則呼叫linkToParents方法,巢狀查詢或巢狀對映,將返回結果設定到父物件的對應屬性中,儲存過程相關,本文暫不分析
  2. 呼叫callResultHandler方法,普通對映,將結果物件儲存到 ResultHandler 中

來看到第2步呼叫的方法

4.3.1callResultHandler方法

callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue)方法

普通對映,將結果物件儲存到 ResultHandler 中

private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
    /*
     * 遞增返回結果數量 resultCount
     * 儲存返回結果 resultObject
     */
    resultContext.nextResultObject(rowValue);
    // 將返回結果儲存至 ResultHandler 中
    ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}
  1. resultContext上下文物件物件中暫存返回結果rowValue,並遞增返回結果的數量,可以回到4.handleRowValuesForSimpleResultMap方法看一下

  2. 通過resultHandlerresultContext上下文物件暫存的返回結果進行處理,在DefaultResultHandler中你可以看到

    public class DefaultResultHandler implements ResultHandler<Object> {
    
    	private final List<Object> list;
    
    	public DefaultResultHandler() {
    		list = new ArrayList<>();
    	}
    
    	@Override
    	public void handleResult(ResultContext<?> context) {
    		list.add(context.getResultObject());
    	}
    }
    

    就是往List集合中新增返回結果

結束語

回顧到3.handleRowValues方法中,上面已經對結果集(不含巢狀ResultMap)進行對映的整個過程進行了分析,在defaultResultHandlermultipleResults都可以獲取到對映後的結果

本來想繼續分析結果集(含巢狀ResultMap)這種情況的,呼叫的是handleRowValuesForNestedResultMap方法,由於上面已經巢狀太多層方法了,就不再分析第二種更復雜的情況,本文字身就不好編排,再進行巢狀就無法閱讀了?,可以參考DefaultResultSetHandler.java根據註釋,進行理解

其中涉及到的DefaultResultContextDefaultResultHandler都比較簡單,也不列出來了,自行根據註釋檢視一下

總結

本文分析了DefaultResultSetHandler是如何處理結果集,進行對映,轉換成Java物件的,總的來說就是根據ResultMap物件,對於不同的場景進行處理分析,對映成我們需要的Java物件,需要考慮的情況太多,所以這個類比較複雜,這裡也僅對結果集(不含巢狀ResultMap)進行對映的整個過程進行了分析,關於含巢狀ResultMap的結果集來說,可能更加稍微複雜一點,不過也相差無幾,可以參考DefaultResultSetHandler.java

到這裡SQL執行過程算是結束了,但是其中還有一部分延遲載入的內容沒有分析,本來準備在這篇文件中分析的,發現已經寫太多內容了,所以放在下一篇文件中

參考文章:芋道原始碼《精盡 MyBatis 原始碼分析》

相關文章