該系列文件是本人在學習 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 物件以及延遲載入等等處理過程,這裡將一步一步地進行分析:
- 《SQL執行過程(一)之Executor》
- 《SQL執行過程(二)之StatementHandler》
- 《SQL執行過程(三)之ResultSetHandler》
- 《SQL執行過程(四)之延遲載入》
MyBatis中SQL執行的整體過程如下圖所示:
在 SqlSession 中,會將執行 SQL 的過程交由Executor
執行器去執行,過程大致如下:
- 通過
DefaultSqlSessionFactory
建立與資料庫互動的SqlSession
“會話”,其內部會建立一個Executor
執行器物件 - 然後
Executor
執行器通過StatementHandler
建立對應的java.sql.Statement
物件,並通過ParameterHandler
設定引數,然後執行資料庫相關操作 - 如果是資料庫更新操作,則可能需要通過
KeyGenerator
先設定自增鍵,然後返回受影響的行數 - 如果是資料庫查詢操作,則需要將資料庫返回的
ResultSet
結果集物件包裝成ResultSetWrapper
,然後通過DefaultResultSetHandler
對結果集進行對映,最後返回 Java 物件
上面還涉及到一級快取、二級快取和延遲載入等其他處理過程
SQL執行過程(三)之ResultSetHandler
高能預警 ❗️ ❗️ ❗️ ❗️ ❗️ ❗️
DefaultResultSetHandler(結果集處理器)將資料庫查詢結果轉換成 Java 物件是一個非常繁瑣的過程,需要處理各種場景,如果繼續往下看,請做好心理準備??
可以先跳轉到 DefaultResultSetHandler,檢視流程圖
在前面SQL執行過程一系列的文件中,已經詳細地分析了在MyBatis的SQL執行過程中,SqlSession會話將資料庫操作交由Executor執行器去完成,然後通過StatementHandler去執行資料庫相關操作,並獲取到資料庫的執行結果
如果是資料庫查詢操作,則需要通過ResultSetHandler
對查詢返回的結果集進行對映處理,轉換成對應的Java物件,算是SQL執行過程的最後一步,那麼我們來看看MyBatis是如何完成這個繁雜的解析過程的
ResultSetHandler介面的實現類如下圖所示:
先回顧一下ResultSetHandler
在哪被呼叫,在PreparedStatementHandler
的query
方法中,程式碼如下:
@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
步可以看到 - 呼叫
resultSetHandler
的handleResultSets(Statement stmt)
方法,對結果集進行對映,轉換成Java物件並返回
ResultSetWrapper
因為在
DefaultResultSetHandler
中,對ResultSet
的操作更多的是它的ResultSetWrapper
包裝類,所以我們先來看看這個類
org.apache.ibatis.executor.resultset.ResultSetWrapper
:java.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 TypetypeHandlerMap
:結果集中每列對應的型別處理器mappedColumnNamesMap
:儲存每個ResultMap物件中對映的列名集合,也就是我們在<resultMap />
標籤下的子標籤配置的column
屬性unMappedColumnNamesMap
:儲存每個ResultMap物件中未對映的列名集合,也就是沒有在<resultMap />
標籤下配置過,但是查詢結果返回了
在構造方法中,會初始化上面的columnNames
、classNames
和jdbcTypes
屬性
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;
}
大致邏輯如下:
- 先從
Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap
屬性中獲取型別處理器 - 如果從快取中沒有獲取到,則嘗試根據Jdbc Type和Java Type從
typeHandlerRegistry
登錄檔獲取 - 如果還是沒有獲取到,則根據
classNames
中拿到結果集中該列的Java Type,然後在從typeHandlerRegistry
登錄檔獲取 - 還是沒有獲取到,則設定為
ObjectTypeHandler
- 最後將其放入
typeHandlerMap
快取中
loadMappedAndUnmappedColumnNames方法
loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix)
方法,初始化mappedColumnNamesMap
和unMappedColumnNamesMap
兩個屬性,分別為對映的列名和未被對映的列名,方法如下:
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);
}
-
獲取配置的列名的字首,全部大寫,通常是沒有配置的
-
獲取
ResultMap
中配置的所有列名,並新增字首如果在
<select />
上面配置的是resultType
屬性,則返回的是空集合,因為它建立的ResultMap
物件中只有Java Type屬性 -
遍歷結果集中所有的列名,如果在
<resultMap />
標籤中的子標籤配置的column
屬性有包含這個列名,則屬於對映的列名 -
否則就屬於未被對映的列名
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
處理結果集的方法的流程圖:
構造方法
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);
}
-
multipleResults
用於儲存對映結果集得到的結果隊形,多 ResultSet 的結果集合,每個 ResultSet 對應一個 Object 物件,而實際上,每個 Object 是List<Object>
物件 -
獲取
ResultSet
物件,並封裝成ResultSetWrapper
-
獲得當前 MappedStatement 物件中的
ResultMap
集合,XML 對映檔案中<resultMap />
標籤生成的,或者 配置 "resultType" 屬性也會生成對應的ResultMap
物件在
<select />
標籤配置ResultMap
屬性時,可以以逗號分隔配置多個,如果返回多個 ResultSet 則會一一對映,通常配置一個 -
如果有返回結果,但是沒有
ResultMap
接收物件則丟擲異常 -
呼叫
handleResultSet
方法,完成結果集的對映,全部轉換的 Java 物件,儲存至multipleResults
集合中,或者this.resultHandler
中(使用者自定的,通常不會) -
獲取 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());
}
}
-
暫時忽略,因為只有儲存過程的情況時 parentMapping 為非空,檢視上面的1.handleResultSets方法的第
6
步 -
使用者沒有指定
ResultHandler
結果處理器- 建立
DefaultResultHandler
預設結果處理器,就是使用一個List集合儲存轉換後的Java物件 - 呼叫
handleRowValues
方法,處理結果集,進行一系列的處理,完成對映,將結果儲存至 DefaultResultHandler 中 - 將結果集合新增至
multipleResults
中
- 建立
-
使用者指定了自定義的
ResultHandler
結果處理器,和第2
步的區別在於,處理後的Java物件不會儲存在multipleResults
中,僅儲存在ResultHandler
中,使用者可通過它獲取 -
關閉 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);
}
}
-
如果當前 ResultMap 存在內嵌的 ResultMap
例如
<resultMap />
標籤中<association />
或者<collection />
都會建立對應的 ResultMap 物件,該物件的 id 會設定到ResultMapping
的nestedResultMapId
屬性中,這就屬於內嵌的 ResultMap- 如果不允許在巢狀語句中使用分頁,則對 rowBounds 進行校驗,設定了 limit 或者 offset 則丟擲異常,預設允許
- 校驗要不要使用自定義的 ResultHandler,針對內嵌的 ResultMap
- 處理結果集,進行對映,生成返回結果,儲存至
resultHandler
或者設定到parentMapping
(儲存過程相關,本文暫不分析)的對應屬性中,這裡會對內嵌的 ResultMap 進行處理,呼叫handleRowValuesForNestedResultMap
方法
-
處理結果集,進行對映,生成返回結果,儲存至
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
:控制是否還進行對映
-
根據
RowBounds
中的offset
跳到到結果集中指定的記錄 -
檢測已經處理的行數是否已經達到上限(
RowBounds.limit
)以及ResultSet
中是否還有可處理的記錄 -
呼叫
resolveDiscriminatedResultMap
方法,獲取最終的 ResultMap因為 ResultMap 可能使用到了
<discriminator />
標籤,需要根據不同的值對映不同的 ResultMap
如果存在Discriminator
鑑別器,則根據當前記錄選擇對應的 ResultMap,會一直巢狀處理 -
呼叫
getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
方法,從結果集中獲取到返回結果物件,進行對映,比較複雜,關鍵方法!!! -
呼叫
storeObject
方法,將返回結果物件儲存至resultHandler
,或者設定到父物件parentMapping
(儲存過程相關,本文暫不分析)的對應屬性中
對於第3
、4
、5
步的三個方法,我們一個一個來看
-
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;
}
-
獲取 ResultMap 中的
Discriminator
鑑別器,<discriminator />
標籤會被解析成該物件 -
呼叫
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)); }
-
Discriminator
鑑別器根據該值獲取到對應的 ResultMap 的 id- 存在對應的 ResultMap 物件,則獲取到
- 記錄上一次的鑑別器
- 獲取到對應 ResultMap 內的鑑別器,可能鑑別器裡面還有鑑別器
- 檢測是否出現迴圈巢狀了
-
Discriminator
鑑別結果沒有對應的 ResultMap,則直接跳過 -
返回最終使用的 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;
}
-
建立一個儲存延遲載入的集合
ResultLoaderMap
物件lazyLoader
,如果存在代理物件,建立的代理物件則需要通過它來執行需要延遲載入的方法,在後續會將到? -
呼叫
createResultObject
方法,建立返回結果的例項物件rowValue
(如果存在巢狀子查詢且是延遲載入則為其建立代理物件,後續的延遲載入儲存至lazyLoader
中即可) -
如果上面建立的返回結果的例項物件
rowValue
不為 null,並且沒有對應的 TypeHandler 型別處理器,則需要對它進行賦值例如我們返回結果為 java.lang.String 就不用了,因為上面已經處理且賦值了
- 將返回結果的例項物件封裝成 MetaObject 物件
metaObject
,便於操作 - 標記是否成功對映了任意一個屬性,
useConstructorMappings
表示是否在構造方法中使用了引數對映 - 呼叫
shouldApplyAutomaticMappings
方法,檢測是否需要自動對映,就是對未被對映的列進行處理 - 呼叫
applyAutomaticMappings
方法,從結果集中將未被對映的列值設定到返回結果metaObject
中,返回是否對映成功(設定了1個或以上的屬性值) - 呼叫
applyPropertyMappings
方法,從結果集中將 ResultMap 中需要對映的列值設定到返回結果metaObject
中,返回是否對映成功(設定了1個或以上的屬性值) - 如果沒有成功對映任意一個屬性,則根據
returnInstanceForEmptyRow
全域性配置(預設為false)返回空物件還是 null
- 將返回結果的例項物件封裝成 MetaObject 物件
-
返回該結果物件
rowValue
我們逐步來看上面的第2
、3.3
、3.4
、3.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;
}
-
記錄構造方法的入參型別
-
記錄構造方法的引數值
-
呼叫
createResultObject
方法(過載),建立返回結果的例項物件,該步驟的核心!!! -
如果返回結果的例項物件不為空,且返回結果沒有對應的 TypeHandler 型別處理器,例如一個實體類,則遍歷所有的對映列,如果存在巢狀子查詢並且要求延遲載入,那麼為該返回結果的例項物件建立一個動態代理物件(Javassist)
這樣一來可以後續將需要延遲載入的屬性放入
lazyLoader
中即可,在後續會講到? -
記錄是否使用有參構造方法建立的該返回結果例項物件,就是使用了對映,後續判斷返回空物件還是null需要用到
-
返回例項物件,也可能是它的動態代理物件
這裡我們需要來看到第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種場景:
-
結果集只有一列,且存在對應的 TypeHandler 型別處理器,例如返回 java.lang.String
則呼叫
createPrimitiveResultObject
方法,將該列轉換成對應 Java Type 的值,然後返回 -
<resultMap />
標籤下配置的<constructor />
標籤下的建構函式引數資訊不為空則呼叫
createParameterizedResultObject
方法,根據<constructor /
標籤下的構造方法入參配置,嘗試從結果集中獲取入參值,並建立返回結果的例項物件 -
返回型別為介面,或者有預設的構造方法
則通過例項工廠
objectFactory
,使用預設無參構造方法建立返回結果的例項物件 -
找到合適的構造方法
則呼叫
createByConstructorSignature
方法,找到合適的構造方法並建立返回結果物件
好的,接下來我們又要看到第1
、2
、4
步呼叫的三個方法了
- 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);
}
- 通過
ResultSetWrapper
根據Java Type和columnName找到對應的TypeHandler
型別處理器 - 通過
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;
}
-
需要先從結果集中獲取每個
<constructor />
標籤配置的引數對應的值,這裡又可能存在以下三種情況:- 該引數存在巢狀查詢,則呼叫
getNestedQueryConstructorValue
方法,獲取到該屬性值 - 存在巢狀 ResultMap,則呼叫
getRowValue
方法,從該結果集中獲取到巢狀 ResultMap 對應的值,回到了4.2getRowValue方法 - 正常情況,通過TypeHandler型別處理器,根據列名從結果集中獲取到該屬性值
- 該引數存在巢狀查詢,則呼叫
-
通過
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;
}
-
獲得巢狀查詢關聯的 id
-
獲取巢狀查詢對應的 MappedStatement 物件
-
獲取巢狀查詢的引數型別
-
獲取巢狀查詢的引數物件,已完成初始化,呼叫
prepareParameterForNestedQuery
方法,進去後發現又得兩層方法?,這裡就不再展開了,比較簡單,可以先參考的我的註釋檢視,在後續還會呼叫該方法,再進行解析 -
執行查詢,因為這裡的構造方法中的入參,所以無需判斷延遲載入,在後面設定屬性時就不一樣了
- 獲取巢狀查詢中的 SQL 物件
- 獲取CacheKey物件
- 建立 ResultLoader 物件
- 載入結果
-
返回子查詢返回的值
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());
}
- 獲取所有的建構函式
- 找到新增了
@AutomapConstructor
註解的構造方法,如果存在則呼叫createUsingConstructor
方法,建立一個例項物件 - 否則,遍歷所有的構造方法
- 如果構造方法的入參與結果集中列的個數相同,並且入參的 Java Type 和列的 Jdbc Type 有型別處理器
- 使用這個構造方法建立返回結果的例項物件,呼叫
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();
}
}
}
- 如果
<resultMap />
中的autoMapping
配置不為空,則返回該配置 - 否則通過全域性配置來判斷,預設
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;
}
- 呼叫
createAutomaticMappings
方法,將這些未被對映的欄位建立對應的UnMappedColumnAutoMapping
物件(包含列名、屬性名、型別處理器、是否為原始型別) - 遍歷未被對映的欄位陣列,將這些屬性設定到返回結果物件中
- 通過 TypeHandler 型別處理器獲取未被對映的欄位的值
- 如果屬性值不為空,或者配置了值為 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;
}
-
ResultMap 中需要 "自動對映" 的列會快取起來,這是對應的快取 key
-
先從
autoMappingsCache
快取中獲取該 ResultMap 對應的UnMappedColumnAutoMapping
集合autoMapping
,沒有的話才進行接下來的解析 -
獲取未對映的的列名集合,也就是資料庫返回的列名在 ResultMap 中沒有配置,例如我們配置的是 resultType 屬性就全部沒有配置,然後進行遍歷
-
如果配置了字首,則將列名中的字首去掉作為屬性名
-
根據列名從入參物件中獲取對應的屬性名稱,不管大小寫都可以找到
-
開始為該屬性建立
UnMappedColumnAutoMapping
物件,如果返回物件中有該屬性的 setter 方法- 獲取屬性名稱的 Class 物件
- 如果有對應的 TypeHandler 型別處理器,建立該屬性的
UnMappedColumnAutoMapping
物件,設定列名、屬性名、型別處理器、是否為原始型別,新增到autoMapping
集合中 - 否則,執行發現自動對映目標為未知列(或未知屬性型別)的行為,預設為
NONE
,不做任何行為
-
該屬性沒有setter方法,執行發現自動對映目標為未知列(或未知屬性型別)的行為,預設為
NONE
,不做任何行為 -
返回
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;
}
-
獲取 ResultMap 中明確需要進行對映的列名集合
mappedColumnNames
-
獲取 ResultMap 中所有的 ResultMapping 物件,然後進行遍歷
-
從結果集獲取屬性值設定到返回結果中,需要滿足下面三個條件中的一個:
- 配置的
column
屬性為{prop1:col1,prop2:col2}
這種形式,一般就是巢狀子查詢,表示將col1和col2的列值設定到巢狀子查詢的入參物件的prop1和prop2屬性中 - 基本型別的屬性對映
- 多結果集的場景處理,該屬性來自另一個結果集,儲存過程相關,本文暫不分析
- 配置的
-
完成對映,呼叫
getPropertyMappingValue
方法,從結果集中獲取到對應的屬性值value
- 沒有配置對應的 Java 屬性則跳過
- 如果是
DEFERRED
佔位符(延遲載入),則跳過 - 將屬性值設定到返回結果中
我們來看看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
屬性配置可能有三種情況:
- 如果是巢狀子查詢,則呼叫
getNestedQueryMappingValue
方法,執行巢狀子查詢,返回查詢結果,如果需要延遲記載則返回的是DEFERRED
- 如果是多結果集,儲存過程相關,則呼叫
addPendingChildRelation
方法, 多結果集處理,延遲載入,返回佔位符,本文暫不分析 - 正常情況,獲取 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;
}
-
獲取巢狀子查詢關聯的ID、屬性名、巢狀子查詢的
MappedStatement
物件、巢狀子查詢的引數型別 -
從結果集中獲取引數值,準備好巢狀子查詢的入參,呼叫
prepareParameterForNestedQuery
方法 -
獲得巢狀子查詢的 BoundSql 物件
-
獲得巢狀子查詢本次查詢的 CacheKey 物件、巢狀子查詢的返回 Java Type
-
檢查快取中已存在本次子查詢的資料,已存在的話則進行下面兩步
-
則建立
DeferredLoad
物件,並通過該DeferredLoad
物件從快取中載入結果物件這也算延遲載入,巢狀子查詢的結果在快取中,然後會在查詢介面後進行載入,可以回到《SQL執行過程(一)之Executor》的BaseExecutor小節中的
query
方法的第6
步看看 -
返回
DEFERRED
延遲載入預設物件
-
-
快取中不存在本次子查詢的資料
-
建立
ResultLoader
物件resultLoader
-
如何該屬性還要求了是延遲載入
-
則將其新增到
ResultLoader.loaderMap
中,等待真正使用時再執行巢狀查詢並得到結果物件可以回到4.2.1createResultObject方法的第
4
步看一下,如果存在巢狀子查詢並且要求延遲載入,那麼為該返回結果的例項物件建立一個動態代理物件(Javassist),後續將需要延遲載入的屬性放入lazyLoader
(就是上面的ResultLoader)中即可 -
返回
DEFERRED
延遲載入預設物件
-
-
否在直接載入
resultLoader
物件,獲取到該屬性值
-
-
最後返回該屬性值或者
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);
}
}
-
巢狀子查詢是有多個屬性對映,則呼叫
prepareCompositeKeyParameter
方法,從結果集中獲取多個屬性值設定到入參物件中配置的
column
屬性為{prop1:col1,prop2:col2}
這種形式,表示將col1和col2的列值設定到巢狀子查詢的入參物件的prop1和prop2屬性中 -
只有一個屬性對映,則呼叫
prepareSimpleKeyParameter
方法,從結果集中直接獲取巢狀查詢的入參
來看看第1
、2
步呼叫的方法
- 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;
}
-
建立一個巢狀子查詢的入參的例項物件
parameterObject
,呼叫instantiateParameterObject
方法,很簡單,點選去看一下就知道了 -
開始遍歷ResultMapping中的
List<ResultMapping> composites
組合欄位 -
獲取巢狀子查詢的入參該屬性對應的 TypeHandler 處理器
-
通過 TypeHandler 根據該屬性的
column
列名從該結果集中獲取值 -
設定屬性值到子查詢的入參物件中
-
返回
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));
}
- 根據Java Type獲取到 TypeHandler 型別處理器
- 根據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);
}
}
- 如果
parentMapping
不為空,則呼叫linkToParents
方法,巢狀查詢或巢狀對映,將返回結果設定到父物件的對應屬性中,儲存過程相關,本文暫不分析 - 呼叫
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);
}
-
在
resultContext
上下文物件物件中暫存返回結果rowValue
,並遞增返回結果的數量,可以回到4.handleRowValuesForSimpleResultMap方法看一下 -
通過
resultHandler
對resultContext
上下文物件暫存的返回結果進行處理,在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)進行對映的整個過程進行了分析,在defaultResultHandler
和multipleResults
都可以獲取到對映後的結果
本來想繼續分析結果集(含巢狀ResultMap)這種情況的,呼叫的是handleRowValuesForNestedResultMap
方法,由於上面已經巢狀太多層方法了,就不再分析第二種更復雜的情況,本文字身就不好編排,再進行巢狀就無法閱讀了?,可以參考DefaultResultSetHandler.java根據註釋,進行理解
其中涉及到的DefaultResultContext
和DefaultResultHandler
都比較簡單,也不列出來了,自行根據註釋檢視一下
總結
本文分析了DefaultResultSetHandler
是如何處理結果集,進行對映,轉換成Java物件的,總的來說就是根據ResultMap物件,對於不同的場景進行處理分析,對映成我們需要的Java物件,需要考慮的情況太多,所以這個類比較複雜,這裡也僅對結果集(不含巢狀ResultMap)進行對映的整個過程進行了分析,關於含巢狀ResultMap的結果集來說,可能更加稍微複雜一點,不過也相差無幾,可以參考DefaultResultSetHandler.java
到這裡SQL執行過程算是結束了,但是其中還有一部分延遲載入的內容沒有分析,本來準備在這篇文件中分析的,發現已經寫太多內容了,所以放在下一篇文件中
參考文章:芋道原始碼《精盡 MyBatis 原始碼分析》