詳解MyBatis?ResultSetHandler?結(jié)果集的解析過(guò)程
正文
mybatis版本:3.5.12
mybatis通過(guò)Executor查詢(xún)出結(jié)果后,通常返回的是一個(gè)List結(jié)構(gòu),再根據(jù)用戶(hù)調(diào)用的API把List結(jié)構(gòu)轉(zhuǎn)為指定結(jié)構(gòu)。
- 比如用戶(hù)調(diào)用
SqlSession#selectOne就是List中只有一條數(shù)據(jù),如果查詢(xún)得到多條數(shù)據(jù)會(huì)拋出TooManyResultsException的異常。 - 比如用戶(hù)調(diào)用
SqlSession#selectMap就是遍歷List中的每個(gè)元素,把這些元素轉(zhuǎn)換成key-value形式的Map結(jié)構(gòu)并返回 - 或者用戶(hù)自定義返回一個(gè)User對(duì)象,也會(huì)遍歷List,把元素轉(zhuǎn)換為指定類(lèi)型的對(duì)象
mybatis中封裝了一個(gè)類(lèi)叫做ResultSetHandler它用來(lái)處理查詢(xún)數(shù)據(jù)庫(kù)得到的結(jié)果集,并把結(jié)果集解析為用戶(hù)指定類(lèi)型的數(shù)據(jù)。它的調(diào)用時(shí)機(jī)就是在查詢(xún)玩數(shù)據(jù)庫(kù)之后,調(diào)用時(shí)機(jī)如下
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
第一步先獲取PreparedStatement對(duì)象,第二部執(zhí)行execute方法查詢(xún)數(shù)據(jù)庫(kù),第三步就是使用ResultSetHandler處理結(jié)果集。接下來(lái)就來(lái)看下resultSetHandler是如何處理結(jié)果集對(duì)象的。
它的邏輯在ResultSetHandler#handleResultSets方法中
ResultSetHandler#handleResultSets
ResultSetHandler是一個(gè)接口,它只有一個(gè)實(shí)現(xiàn)類(lèi)DefaultResultSetHandler,下面是handleResultSets方法關(guān)的鍵代碼。我把此核心邏輯代碼分為了5部分,后面章節(jié)詳細(xì)介紹
public List<Object> handleResultSets(Statement stmt) throws SQLException {
// 第一部分:用來(lái)緩存最后的返回值,每條記錄處理完之后都會(huì)存入該集合中
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
/*
* ResultSetWrapper對(duì)結(jié)果集(ResultSet)進(jìn)行了包裝
* getFirstResultSet獲取了Statement中的第一個(gè)結(jié)果集對(duì)象,注:一般情況下只有一個(gè)結(jié)果集,如果調(diào)用存儲(chǔ)過(guò)程可能就會(huì)獲得多個(gè)結(jié)果集
*/
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 第二部分
// 1. 先處理mappedStatement中的ResultMap標(biāo)簽(每個(gè)XML的SQL語(yǔ)句都被映射成了MappedStatement對(duì)象。)
// 每個(gè)SQL執(zhí)行的返回結(jié)果有可能是多個(gè)resultMap標(biāo)簽共同組成的??赡苁嵌嘟Y(jié)果集
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
// 第三部分
while (rsw != null && resultMapCount > resultSetCount) {
// MappedStatement中的ResultMap數(shù)量應(yīng)該和 結(jié)果集的數(shù)量一致
ResultMap resultMap = resultMaps.get(resultSetCount);
// 處理結(jié)果集,這是該方法中最重要的步驟
handleResultSet(rsw, resultMap, multipleResults, null);
// 獲取下一個(gè)結(jié)果集(多結(jié)果集情況)
rsw = getNextResultSet(stmt);
// nestedResultObjects清空該對(duì)象,該對(duì)象是一個(gè)緩存
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 第四部分
// 2. 先處理mappedStatement中的ResultSets標(biāo)簽. 因?yàn)榻馕鯮esultMap的時(shí)候,可能ResultMap中包含ResultSet標(biāo)簽,而ResultSet標(biāo)簽并未解析
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
// 第五部分
return collapseSingleResultList(multipleResults);
}
該代碼主要分為兩個(gè)大邏輯:
- 通過(guò)getFirstResultSet方法獲取第一個(gè)結(jié)果集對(duì)象,然后循環(huán)ps中的結(jié)果集,處理每個(gè)結(jié)果集。每個(gè)結(jié)果集處理完后的數(shù)據(jù)存放到multipleResults這個(gè)集合中
- 處理多結(jié)果集剩余的部分。因?yàn)橛脩?hù)可能使用了resultSets標(biāo)簽。返回2個(gè)結(jié)果集,但是在處理第一個(gè)結(jié)果集映射成用戶(hù)指定類(lèi)型時(shí),需要用到第二個(gè)結(jié)果集對(duì)象,這在第一步是無(wú)法完成的。只能在第二部完成。
比如有如下存儲(chǔ)函數(shù):getuserand_orders
create procedure get_user_and_orders(in id int)
begin
select * from user;
select * from order;
END;
該函數(shù)的業(yè)務(wù)意義是:查詢(xún)所有的用戶(hù),和所有的訂單。在Mapper中定義的resultMap如下
<resultMap id="userMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="password" column="password"/>
<association property="orderList" resultSet="orders">
<result property="name" column="name"/>
</association>
</resultMap>
<!--resultSets的順序不能隨意放置,否則會(huì)導(dǎo)致結(jié)果集為空-->
<select id="selectMoreResults2" statementType="CALLABLE" resultSets="users,orders" resultMap="userMap">
{call get_user_and_orders(1)}
</select>
此時(shí)如果用戶(hù)執(zhí)行了存儲(chǔ)函數(shù),那么PS中的結(jié)果集 會(huì)有兩個(gè),分別是users和orders。mybatis在處理結(jié)果集時(shí)發(fā)現(xiàn)。結(jié)果集中有兩個(gè)對(duì)象,先處理第一個(gè),第一個(gè)結(jié)果集為users,自然要映射為User對(duì)象,給User對(duì)象的orderList屬性賦值時(shí)發(fā)現(xiàn)結(jié)果集中沒(méi)有關(guān)于訂單的數(shù)據(jù),因?yàn)橛唵蔚臄?shù)據(jù)在第二個(gè)結(jié)果集中。這時(shí)候就會(huì)在第二部再去處理第二個(gè)結(jié)果集。把訂單的結(jié)果集數(shù)據(jù)映射到User的orderList屬性中。
下面我們?cè)敿?xì)分析上面這一長(zhǎng)串代碼。
第一部分:ResultSetWrapper
首先我們來(lái)看第一部分的三行代碼
final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt);
- 首先定義了一個(gè)List類(lèi)型的集合multipleResults,結(jié)果集中每一條記錄解析完畢后的數(shù)據(jù)都會(huì)存放到該集合中
- 定義變量resultSetCount,它代表結(jié)果集的個(gè)數(shù)。(結(jié)果集的個(gè)數(shù)不一定等于ResultMaps的個(gè)數(shù)哦)
- 把結(jié)果集對(duì)象封裝為一個(gè)ResultSetWrapper對(duì)象。ResultSetWrapper其實(shí)就是對(duì)JDBC中的ResultSet對(duì)象做了一個(gè)封裝。包裝了一些元數(shù)據(jù)的信息。下面來(lái)看下ResultSetWrapper的重要結(jié)構(gòu)
public class ResultSetWrapper {
private final ResultSet resultSet;
private final TypeHandlerRegistry typeHandlerRegistry;
// 結(jié)果集中的列名集合
private final List<String> columnNames = new ArrayList<>();
// java名稱(chēng)集合
private final List<String> classNames = new ArrayList<>();
private final List<JdbcType> jdbcTypes = new ArrayList<>();
// ResultMap標(biāo)簽中指定的映射(重要?。?
private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
// ResultMap標(biāo)簽中未指定的映射字段(重要?。?
private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();
}
- resultSet:JDBC中的結(jié)果集對(duì)象
- TypeHandlerRegistry:類(lèi)型處理器,用于JDBC和Java類(lèi)型的轉(zhuǎn)換
- columnNames:結(jié)果集中的所有列名的集合
- classNames:每一列對(duì)應(yīng)的Java類(lèi)型的集合
- jdbcTypes:每一列對(duì)應(yīng)的JDBC類(lèi)型的結(jié)合
- mappedColumnNamesMap:resultMap標(biāo)簽中顯式定義的標(biāo)簽Map
- unMappedColumnNamesMap:結(jié)果集中返回但resultMap標(biāo)簽中未定義的列會(huì)被記錄在該Map中
第二部分:驗(yàn)證rsw對(duì)象
上面獲取了rsw對(duì)象(ResultSetWrapper,后面簡(jiǎn)稱(chēng)rsw了)后,接下來(lái)需要驗(yàn)證rsw對(duì)象。第二部分的三行代碼如下
List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount);
- 首先先從mappedStatement對(duì)象中獲取ResultMap對(duì)象,首先mappedStatement可以理解為XML中的每個(gè)
select|update|insert|delete節(jié)點(diǎn)都被封裝成了MS對(duì)象(mappedStatement簡(jiǎn)稱(chēng)MS)。那么MS對(duì)象中其實(shí)就包含了每個(gè)select|update|insert|delete節(jié)點(diǎn)的信息。而一個(gè)節(jié)點(diǎn)可能會(huì)在resultMap標(biāo)簽上定義多個(gè)返回結(jié)果集。比如下面代碼在select標(biāo)簽的resultMap屬性中定義了兩個(gè)結(jié)果集
<select id="selectMoreResults1" statementType="CALLABLE" resultMap="users,authors">
{call get_user_and_authors(1)}
</select>
- 定義變量resultMapCount,它表示一個(gè)MS對(duì)象中resultMap的個(gè)數(shù)。通常它是1。我們常用的情況也是1。要注意,第一部分定義的resultSetCount變量和resultMapCount并不一定相等。比如PreparedStatement對(duì)象中有兩個(gè)結(jié)果集——那么此時(shí)的resultSetCount就是2.但是xml中select標(biāo)簽的resultMap屬性只定義了一個(gè)映射——那么此時(shí)的resultMapCount就是1
- 當(dāng)resultMapCount < resultSetCount的時(shí)候,就說(shuō)明多個(gè)結(jié)果集對(duì)應(yīng)了XML中的一個(gè)映射關(guān)系,此時(shí)就需要解析resultSet標(biāo)簽
- 最后一件事是驗(yàn)證rsw是否合法,代碼比較簡(jiǎn)單就不詳細(xì)介紹了
第三部分:遍歷rsw中的結(jié)果集
接下來(lái)就是要遍歷rsw中的結(jié)果集對(duì)象。并把結(jié)果集中的每條記錄都根據(jù)resultMap標(biāo)簽定義的映射關(guān)系轉(zhuǎn)化為指定類(lèi)型的數(shù)據(jù)。并把它加入到第一部分提到的multipleResults集合中。第三部分的代碼如下
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 處理結(jié)果集,這是該方法中最重要的步驟
handleResultSet(rsw, resultMap, multipleResults, null);
// 獲取下一個(gè)結(jié)果集(多結(jié)果集情況)
rsw = getNextResultSet(stmt);
// nestedResultObjects清空該對(duì)象,該對(duì)象是一個(gè)緩存
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
改代碼的意思是,當(dāng)rsw存在并且resultMapCount > resultSetCount時(shí)
- 獲取結(jié)果集對(duì)應(yīng)的ResultMap對(duì)象
- 調(diào)用handleResultSet方法處理結(jié)果集對(duì)象(這個(gè)方法很重要,它實(shí)際上完成了結(jié)果集中的每條記錄的解析,它其中又調(diào)用了很多重要的方法,該方法后面我會(huì)單獨(dú)抽出一篇文章來(lái)講)
- 獲取下一個(gè)結(jié)果集并且空緩存對(duì)象。nestedResultObjects是解析嵌套映射中的一個(gè)緩存對(duì)象(了解即可)每次解析完一個(gè)結(jié)果集后都要清空該對(duì)象。
- 重復(fù)上述步驟。不過(guò)一般我們都是執(zhí)行單條SQL語(yǔ)句,所以PreparedStatement一般只有一個(gè)結(jié)果集,該循環(huán)也只會(huì)走一次。除非調(diào)用了存儲(chǔ)函數(shù)
第四部分:處理ResultSets標(biāo)簽
如果在第一部分到第三部分的循環(huán)中,順序處理完結(jié)果集對(duì)象之后,resultSetCount數(shù)量還是大于resultMapCount,那么就證明PS對(duì)象返回的是多結(jié)果集,并且多結(jié)果集值對(duì)應(yīng)了一個(gè)映射關(guān)系,此時(shí)就需要解析這個(gè)ResultSets標(biāo)簽。它的解析流程和第三部分一樣,重點(diǎn)就在于handleResultSet方法。下面使用一個(gè)案例來(lái)詳細(xì)說(shuō)明為什么會(huì)有這部分的解析。
- 定義一個(gè)存儲(chǔ)函數(shù)
create procedure get_user_and_orders(in id int)
begin
select * from user;
select * from order;
END;
- xml中配置調(diào)用存儲(chǔ)函數(shù)的select節(jié)點(diǎn)
<resultMap id="userMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="password" column="password"/>
<association property="orderList" resultSet="orders">
<result property="name" column="name"/>
</association>
</resultMap>
<!--resultSets的順序不能隨意放置,否則會(huì)導(dǎo)致結(jié)果集為空-->
<select id="selectMoreResults2" statementType="CALLABLE" resultSets="users,orders" resultMap="userMap">
{call get_user_and_orders(1)}
</select>
- 用戶(hù)調(diào)用selectMoreResults2這個(gè)方法。很顯然selectMoreResults2的返回結(jié)果就是存儲(chǔ)函數(shù)執(zhí)行的結(jié)果,它執(zhí)行了兩個(gè)select語(yǔ)句,意味著會(huì)生成兩個(gè)結(jié)果集對(duì)象,xml中select標(biāo)簽定義該存儲(chǔ)函數(shù)的執(zhí)行結(jié)果值對(duì)應(yīng)一個(gè)映射關(guān)系就是userMap。但是兩個(gè)結(jié)果集怎么映射成一個(gè)resultMap呢?我們真正想要的結(jié)果是把第二個(gè)結(jié)果集映射到userMap中的orderList屬性。所以在進(jìn)行第三部分進(jìn)行遍歷的時(shí)候,循環(huán)只會(huì)走一次,因?yàn)閞esultSetCount=2,resultMapCount=1,讀者可以自定使用該業(yè)務(wù)代碼進(jìn)行斷點(diǎn)調(diào)試。在解析第一個(gè)結(jié)果集時(shí)發(fā)現(xiàn)第一個(gè)結(jié)果集中沒(méi)有orderList的信息。無(wú)法完成映射。所以才會(huì)走到第四部分進(jìn)行結(jié)果集映射!
第五部分:collapseSingleResultList
最后一部分很簡(jiǎn)單,它只是把最后返回的結(jié)果進(jìn)行判斷:如果返回結(jié)果multipleResults集合大小為1,則只返回集合中的這個(gè)元素,否則返回原對(duì)象本身
private List<Object> collapseSingleResultList(List<Object> multipleResults) {
return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
}
總結(jié)
該篇講述了mybatis在執(zhí)行完數(shù)據(jù)庫(kù)后進(jìn)行結(jié)果集的大致解析過(guò)程。
- ResultSetWrapper是對(duì)JDBC中的ResultSet對(duì)象的封裝
- 結(jié)果集解析的重點(diǎn)在
DefaultResultSetHandler#handleResultSet這個(gè)方法中 - XML中的resultMap可以定義多個(gè)映射關(guān)系。如果多個(gè)結(jié)果集對(duì)應(yīng)一個(gè)映射關(guān)系就需要第四部分(對(duì)resultSets標(biāo)簽的處理)
后續(xù)我會(huì)帶來(lái)handleResultSet方法的解析~
更多關(guān)于MyBatis ResultSetHandler結(jié)果集的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java 異常java.lang.NoSuchFieldException解決方案
這篇文章主要介紹了Java 異常java.lang.NoSuchFieldException解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
完整B樹(shù)算法Java實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了完整的B樹(shù)算法Java實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
淺談升級(jí)Spring Cloud到Finchley后的一點(diǎn)坑
這篇文章主要介紹了淺談升級(jí)Spring Cloud到Finchley后的一點(diǎn)坑,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-10-10
springboot如何獲取yml文件的自定義參數(shù)
這篇文章主要介紹了springboot如何獲取yml文件的自定義參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
SpringMVC攔截器實(shí)現(xiàn)登錄認(rèn)證
這篇文章主要介紹了SpringMVC攔截器實(shí)現(xiàn)登錄認(rèn)證的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Idea中Java項(xiàng)目如何修改項(xiàng)目名
這篇文章主要介紹了Idea中Java項(xiàng)目如何修改項(xiàng)目名問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06
java向數(shù)據(jù)庫(kù)插入數(shù)據(jù)顯示亂碼的幾種問(wèn)題解決
這篇文章主要給大家介紹了關(guān)于java向數(shù)據(jù)庫(kù)插入數(shù)據(jù)顯示亂碼問(wèn)題的解決方案,文章分別羅列了前臺(tái)亂碼的問(wèn)題、前臺(tái)先后臺(tái)插入數(shù)據(jù)后臺(tái)接收到的數(shù)據(jù)是亂碼以及后臺(tái)向數(shù)據(jù)庫(kù)插入數(shù)據(jù)是亂碼等幾種情況,需要的朋友可以參考下2021-11-11
spring boot springMVC擴(kuò)展配置實(shí)現(xiàn)解析
這篇文章主要介紹了spring boot springMVC擴(kuò)展配置實(shí)現(xiàn)解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08

