MyBatis 源碼分析 - SQL執行過程(三)之 ResultSetHandler


參考 知識星球芋道源碼 星球的源碼解析,一個活躍度非常高的 Java 技術社群,感興趣的小伙伴可以加入 芋道源碼 星球,一起學習😄

該系列文檔是本人在學習 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 源碼分析 - 文章導讀》

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 源碼分析》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM