MyBatis中ResultSetHandler的具體使用
StatementHandler參數(shù)處理&結(jié)果處理,分別委托給ParameterHandler&ResultSetHandler(注意不是ResultHandler)。上一篇文章介紹了ParameterHandler,本文介紹ResultSetHandler。
一、ResultSetHandler接口
我們先看下ResultSetHandler接口定義:
public interface ResultSetHandler {
// 處理結(jié)果集,返回結(jié)果列表
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
// 處理游標結(jié)果集,返回Cursor對象(用于流式處理)
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
// 處理存儲過程的輸出參數(shù)
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
可以看到,ResultSetHandler接口定義了三種不同場景下,處理JDBC結(jié)果集的方法。其核心職責是將數(shù)據(jù)庫返回的ResultSet轉(zhuǎn)換為Java對象。
handleResultSets:核心方法,處理JDBC的Statement的結(jié)果集,注意普通查詢和存儲過程查詢,都使用這個方法的結(jié)果集。handleCursorResultSets:處理Mapper的返回值是Cursor的方法。handleOutputParameters:處理存儲過程的輸出參數(shù),存儲過程會使用handleResultSets處理結(jié)果集,使用本方法,處理輸出類型的參數(shù)。
二、DefaultResultSetHandler 的核心實現(xiàn)
MyBatis僅提供一個默認實現(xiàn)類DefaultResultSetHandler,我們這里分析下核心的handleResultSets方法源碼。
1. 整體流程:handleResultSets 方法
handleResultSets是處理查詢結(jié)果集的核心方法,負責把Statement的結(jié)果集轉(zhuǎn)換為Java對象,需要注意,無論是普通的查詢還是存儲過程查詢,都是調(diào)用這個方法。handleOutputParameters只是處理存儲過程的參數(shù)的(處理參數(shù)類型為OUT/INOUT類型的)。
注意這個方法名字,handleResultSets,有個s,暗示了結(jié)果集可能是多個
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 獲取第一個結(jié)果集
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 獲取MappedStatement中定義的結(jié)果集映射(可能有多個,對應多結(jié)果集)
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
// 第一部分:處理 MappedStatement 中定義的 resultMaps 對應的結(jié)果集
while (rsw != null && resultSetCount < resultMaps.size()) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 處理當前結(jié)果集,映射為Java對象
handleResultSet(rsw, resultMap, multipleResults, null);
// 獲取下一個結(jié)果集(用于存儲過程多結(jié)果集場景)
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 第二部分:處理MappedStatement中resultSets指定的附加結(jié)果集(用于解決N+1)
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
// 解決N+1,向父對象設置屬性。
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, multipleResults, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
// 合并結(jié)果(如果只有一個結(jié)果集,直接返回該列表)
return collapseSingleResultList(multipleResults);
}
核心流程分為兩部分
- 處理MappedStatement中定義的 resultMaps 對應的結(jié)果集。
- 處理MappedStatement中resultSets指定的附加結(jié)果集,用于解決N+1問題。
兩部分都可能是多個?。?!
原來,Mybatis可以支持一次獲取多個結(jié)果集,很神奇。那么如何返回多個結(jié)果集呢?
- Mybatis 如何返回多個結(jié)果集
- 也可以通過存儲過程,這個官網(wǎng)有例子XML 映射器-結(jié)果映射(搜索ResultSets,可以解決N+1問題,這是第二個循環(huán)的來源)。
2. 單結(jié)果集處理:handleResultSet 方法
handleResultSet 負責處理單個結(jié)果集,根據(jù)是否有自定義 ResultHandler 選擇不同的處理邏輯:
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
// 處理嵌套結(jié)果(就是上個步驟中的第二個循環(huán))
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
// 無自定義ResultHandler時,使用默認的DefaultResultHandler收集結(jié)果
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
// 有自定義ResultHandler時,使用其處理結(jié)果
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// 關(guān)閉結(jié)果集
closeResultSet(rsw.getResultSet());
}
}
3. 行記錄映射:handleRowValues 與 getRowValue 方法
無論有無自定義ResultHandler,都是調(diào)用handleRowValues方法:
// 處理結(jié)果集中的行記錄
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {
// 處理嵌套結(jié)果映射(如包含association/collection)
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 處理普通結(jié)果映射(無嵌套)
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
看下處理普通映射的方法(處理嵌套的有點復雜,平時不怎么用,看起來頭暈):
// 處理普通結(jié)果映射(逐行映射)
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
// 跳過RowBounds指定的偏移量,內(nèi)存分頁
skipRows(rsw.getResultSet(), rowBounds);
// 循環(huán)處理每行記錄,直到達到RowBounds的限制或結(jié)果集結(jié)束
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
// 解析鑒別器(discriminator),動態(tài)選擇ResultMap
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
// 將當前行映射為Java對象
Object rowValue = getRowValue(rsw, discriminatedResultMap);
// 存儲對象(調(diào)用ResultHandler處理)
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
Q:discriminator是什么鬼東西??
A:動態(tài)識別返回對象類型,比如我們定義了一個動物類,查詢結(jié)果需要動態(tài)轉(zhuǎn)為阿貓阿狗,就可以使用這個特性,可以在官網(wǎng)中查看使用方法:XML 映射器-結(jié)果映射(搜索discriminator)。
接下來看下核心的getRowValue方法。
// 將單行記錄映射為Java對象
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 創(chuàng)建結(jié)果對象(通過ObjectFactory)
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !typeHandlerRegistry.hasTypeHandler(rowValue.getClass())) {
// 獲取結(jié)果對象的元信息(用于反射設置屬性)
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 處理結(jié)果映射(ResultMapping),設置對象屬性
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 自動映射(未在ResultMap中顯式配置的字段)
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
// 顯式映射(ResultMap中配置的result/association/collection)
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
getRowValue 方法的核心邏輯:
- 創(chuàng)建結(jié)果對象:通過
ObjectFactory實例化目標對象(如User)。 - 自動映射:對
ResultMap中未顯式配置的字段,根據(jù)列名與屬性名的匹配關(guān)系自動映射。 - 顯式映射:根據(jù)
ResultMap中配置的result、association、collection等標簽,通過TypeHandler將ResultSet中的列值轉(zhuǎn)換為對象屬性。
4. applyAutomaticMappings
除了使用ResultMap標簽進行映射,Mybatis還支持自動映射,也就是通過蛇形轉(zhuǎn)駝峰的方式,將數(shù)據(jù)庫字段,映射成Java屬性:
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap,
MetaObject metaObject, String columnPrefix) throws SQLException {
// 1. 創(chuàng)建自動映射列表:篩選出未顯式配置但存在匹配的字段
List<UnMappedColumnAutoMapping> autoMappings = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
if (autoMappings.isEmpty()) {
return false;
}
boolean foundValues = false;
// 2. 遍歷自動映射列表,逐個處理字段
for (UnMappedColumnAutoMapping mapping : autoMappings) {
// 2.1 通過 TypeHandler 從結(jié)果集獲取字段值(并轉(zhuǎn)換為 Java 類型)
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
// 2.2 通過 MetaObject 給對象屬性賦值(支持 null 值處理配置)
if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
// 給屬性賦值(如 user.setName(value))
metaObject.setValue(mapping.property, value);
}
}
return foundValues;
}
關(guān)鍵步驟解析
- 創(chuàng)建自動映射列表(createAutomaticMappings):獲取未映射的字段,與JavaBean的屬性建立映射關(guān)系,并根據(jù)JDBC的類型,綁定TypeHandler,帶有緩存,防止每次都重復映射。
- 通過TypeHandler轉(zhuǎn)換類型 :熟悉的TypeHandler,與上一節(jié)相反,這個是從JDBC轉(zhuǎn)為Java類型。
- 通過MetaObject賦值:利用 MyBatis 的反射工具 MetaObject 給目標對象的屬性賦值,支持配置 callSettersOnNulls(即使值為null也調(diào)用 setter 方法),以及對基本類型(primitive)的特殊處理(避免給基本類型賦null導致異常)。
applyPropertyMappings方法,為Java對象賦值的邏輯與這個相同,但還有些處理嵌套和延遲加載的邏輯,這里不深入研究了。
三、小結(jié)
ResultSetHandler是負責將JDBC 結(jié)果集(ResultSet)轉(zhuǎn)換為Java對象的核心組件。本文對他的實現(xiàn)做了淺顯的分析,復雜的嵌套結(jié)果映射,以及嵌套結(jié)果的延遲加載,本文沒有涉及(有點復雜)。
到此為止,MyBatis四大核心組件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)已經(jīng)分析完成,后續(xù)準備分析下Mybatis的插件機制,這個可能是我們了解Mybatis源碼最重要的用途。
到此這篇關(guān)于MyBatis中ResultSetHandler的具體使用的文章就介紹到這了,更多相關(guān)MyBatis ResultSetHandler內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
WebDriver實現(xiàn)自動化打開IE中的google網(wǎng)頁并實現(xiàn)搜索
這篇文章主要介紹了WebDriver實現(xiàn)自動化打開IE中的google網(wǎng)頁并實現(xiàn)搜索,需要的朋友可以參考下2014-04-04
Springboot結(jié)合Mybatis-Plus實現(xiàn)業(yè)務撤銷回滾功能
本文介紹了如何在Springboot結(jié)合Mybatis-Plus實現(xiàn)業(yè)務撤銷回滾功能,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-12-12

