詳解MyBatis?ResultSetHandler?結(jié)果集的解析過程
正文
mybatis版本:3.5.12
mybatis通過Executor查詢出結(jié)果后,通常返回的是一個(gè)List結(jié)構(gòu),再根據(jù)用戶調(diào)用的API把List結(jié)構(gòu)轉(zhuǎn)為指定結(jié)構(gòu)。
- 比如用戶調(diào)用
SqlSession#selectOne
就是List中只有一條數(shù)據(jù),如果查詢得到多條數(shù)據(jù)會(huì)拋出TooManyResultsException
的異常。 - 比如用戶調(diào)用
SqlSession#selectMap
就是遍歷List中的每個(gè)元素,把這些元素轉(zhuǎn)換成key-value形式的Map結(jié)構(gòu)并返回 - 或者用戶自定義返回一個(gè)User對(duì)象,也會(huì)遍歷List,把元素轉(zhuǎn)換為指定類型的對(duì)象
mybatis中封裝了一個(gè)類叫做ResultSetHandler
它用來處理查詢數(shù)據(jù)庫得到的結(jié)果集,并把結(jié)果集解析為用戶指定類型的數(shù)據(jù)。它的調(diào)用時(shí)機(jī)就是在查詢玩數(shù)據(jù)庫之后,調(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方法查詢數(shù)據(jù)庫,第三步就是使用ResultSetHandler
處理結(jié)果集。接下來就來看下resultSetHandler是如何處理結(jié)果集對(duì)象的。
它的邏輯在ResultSetHandler#handleResultSets
方法中
ResultSetHandler#handleResultSets
ResultSetHandler是一個(gè)接口,它只有一個(gè)實(shí)現(xiàn)類DefaultResultSetHandler,下面是handleResultSets方法關(guān)的鍵代碼。我把此核心邏輯代碼分為了5部分,后面章節(jié)詳細(xì)介紹
public List<Object> handleResultSets(Statement stmt) throws SQLException { // 第一部分:用來緩存最后的返回值,每條記錄處理完之后都會(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ǔ)過程可能就會(huì)獲得多個(gè)結(jié)果集 */ ResultSetWrapper rsw = getFirstResultSet(stmt); // 第二部分 // 1. 先處理mappedStatement中的ResultMap標(biāo)簽(每個(gè)XML的SQL語句都被映射成了MappedStatement對(duì)象。) // 每個(gè)SQL執(zhí)行的返回結(jié)果有可能是多個(gè)resultMap標(biāo)簽共同組成的。可能是多結(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è)大邏輯:
- 通過getFirstResultSet方法獲取第一個(gè)結(jié)果集對(duì)象,然后循環(huán)ps中的結(jié)果集,處理每個(gè)結(jié)果集。每個(gè)結(jié)果集處理完后的數(shù)據(jù)存放到multipleResults這個(gè)集合中
- 處理多結(jié)果集剩余的部分。因?yàn)橛脩艨赡苁褂昧藃esultSets標(biāo)簽。返回2個(gè)結(jié)果集,但是在處理第一個(gè)結(jié)果集映射成用戶指定類型時(shí),需要用到第二個(gè)結(jié)果集對(duì)象,這在第一步是無法完成的。只能在第二部完成。
比如有如下存儲(chǔ)函數(shù):getuserand_orders
create procedure get_user_and_orders(in id int) begin select * from user; select * from order; END;
該函數(shù)的業(yè)務(wù)意義是:查詢所有的用戶,和所有的訂單。在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í)如果用戶執(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é)果集中沒有關(guān)于訂單的數(shù)據(jù),因?yàn)橛唵蔚臄?shù)據(jù)在第二個(gè)結(jié)果集中。這時(shí)候就會(huì)在第二部再去處理第二個(gè)結(jié)果集。把訂單的結(jié)果集數(shù)據(jù)映射到User的orderList屬性中。
下面我們?cè)敿?xì)分析上面這一長串代碼。
第一部分:ResultSetWrapper
首先我們來看第一部分的三行代碼
final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt);
- 首先定義了一個(gè)List類型的集合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ù)的信息。下面來看下ResultSetWrapper的重要結(jié)構(gòu)
public class ResultSetWrapper { private final ResultSet resultSet; private final TypeHandlerRegistry typeHandlerRegistry; // 結(jié)果集中的列名集合 private final List<String> columnNames = new ArrayList<>(); // java名稱集合 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:類型處理器,用于JDBC和Java類型的轉(zhuǎn)換
- columnNames:結(jié)果集中的所有列名的集合
- classNames:每一列對(duì)應(yīng)的Java類型的集合
- jdbcTypes:每一列對(duì)應(yīng)的JDBC類型的結(jié)合
- mappedColumnNamesMap:resultMap標(biāo)簽中顯式定義的標(biāo)簽Map
- unMappedColumnNamesMap:結(jié)果集中返回但resultMap標(biāo)簽中未定義的列會(huì)被記錄在該Map中
第二部分:驗(yàn)證rsw對(duì)象
上面獲取了rsw對(duì)象(ResultSetWrapper,后面簡(jiǎn)稱rsw了)后,接下來需要驗(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)稱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í)候,就說明多個(gè)結(jié)果集對(duì)應(yīng)了XML中的一個(gè)映射關(guān)系,此時(shí)就需要解析resultSet標(biāo)簽
- 最后一件事是驗(yàn)證rsw是否合法,代碼比較簡(jiǎn)單就不詳細(xì)介紹了
第三部分:遍歷rsw中的結(jié)果集
接下來就是要遍歷rsw中的結(jié)果集對(duì)象。并把結(jié)果集中的每條記錄都根據(jù)resultMap標(biāo)簽定義的映射關(guān)系轉(zhuǎn)化為指定類型的數(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ú)抽出一篇文章來講)
- 獲取下一個(gè)結(jié)果集并且空緩存對(duì)象。nestedResultObjects是解析嵌套映射中的一個(gè)緩存對(duì)象(了解即可)每次解析完一個(gè)結(jié)果集后都要清空該對(duì)象。
- 重復(fù)上述步驟。不過一般我們都是執(zhí)行單條SQL語句,所以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è)案例來詳細(xì)說明為什么會(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>
- 用戶調(diào)用selectMoreResults2這個(gè)方法。很顯然selectMoreResults2的返回結(jié)果就是存儲(chǔ)函數(shù)執(zhí)行的結(jié)果,它執(zhí)行了兩個(gè)select語句,意味著會(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é)果集中沒有orderList的信息。無法完成映射。所以才會(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ù)庫后進(jìn)行結(jié)果集的大致解析過程。
- 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ì)帶來handleResultSet
方法的解析~
更多關(guān)于MyBatis ResultSetHandler結(jié)果集的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
windows下使用 intellij idea 編譯 kafka 源碼環(huán)境
這篇文章主要介紹了使用 intellij idea 編譯 kafka 源碼的環(huán)境,本文是基于windows下做的項(xiàng)目演示,需要的朋友可以參考下2021-10-10JPA延遲加載no Session報(bào)錯(cuò)解決分析
這篇文章主要為大家介紹了JPA延遲加載no Session報(bào)錯(cuò)解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09SpringBoot啟動(dòng)多數(shù)據(jù)源找不到合適的驅(qū)動(dòng)類問題
這篇文章主要介紹了SpringBoot啟動(dòng)多數(shù)據(jù)源找不到合適的驅(qū)動(dòng)類問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01Java經(jīng)典設(shè)計(jì)模式之責(zé)任鏈模式原理與用法詳解
這篇文章主要介紹了Java經(jīng)典設(shè)計(jì)模式之責(zé)任鏈模式,簡(jiǎn)單說明了責(zé)任鏈模式的概念、原理,并結(jié)合實(shí)例形式分析了java實(shí)現(xiàn)責(zé)任鏈模式的具體用法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-08-08使用Springboot 打jar包實(shí)現(xiàn)分離依賴lib和配置
這篇文章主要介紹了使用Springboot 打jar包實(shí)現(xiàn)分離依賴lib和配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02Springboot上傳文件時(shí)提示405問題及排坑過程
這篇文章主要介紹了Springboot上傳文件時(shí)提示405問題及排坑過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07SpringMVC跨服務(wù)器上傳文件中出現(xiàn)405錯(cuò)誤的解決
這篇文章主要介紹了SpringMVC跨服務(wù)器上傳文件中出現(xiàn)405錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09