MyBatis?handleResultSet結(jié)果集解析過(guò)程示例
前置知識(shí)
mybatis版本: 3.5.12
之前說(shuō)到mybatis執(zhí)行完SQL后的結(jié)果集是由DefaultResultHandler組件的handleResultSets處理的。今天就來(lái)探討下這個(gè)重要的方法,該方法非常核心,而且內(nèi)容比較多,所以單拎出來(lái)一章。而上文DefaultResultHandler處理結(jié)果集的大致流程請(qǐng)參考:MyBatis ResultSetHandler 結(jié)果集的解析過(guò)程
在理解handleResultSets是怎么對(duì)結(jié)果集進(jìn)行處理時(shí),需要明白MyBatis中這么幾個(gè)組件
- ResultMap和ResultMapping
- ResultHandler和ResultContext
- ResultLoaderMap和ResultLoader(這些是延時(shí)加載相關(guān))
- ProxyFactory(延時(shí)加載相關(guān),用來(lái)為延時(shí)加載對(duì)象創(chuàng)建代理對(duì)象使用的)
ResultMap和ResultMapping
我們經(jīng)常使用MyBatis的xml文件配置SQL信息,在select標(biāo)簽上有一個(gè)屬性是resultMap,它會(huì)指向一個(gè)resultMap
標(biāo)簽,resultMap
標(biāo)簽示例如下
<resultMap id="usermap" type="user" autoMapping="true"> <!--關(guān)閉自動(dòng)映射,那么沒有指定的列名不會(huì)出現(xiàn)在結(jié)果集中--> <id property="id" column="id"/> <result property="username" column="username"/> <result property="birthday" column="birthday"/> <result property="password" column="password"/> </resultMap>
mybatis中每個(gè)resultMap
標(biāo)簽會(huì)被解析成ResultMap對(duì)象。而resultMap
標(biāo)簽中的idresultconstructor
這3種子標(biāo)簽會(huì)被解析成ResultMapping對(duì)象(id其實(shí)就是特殊的result標(biāo)簽)。
接下來(lái)我們來(lái)看下ResultMap的重要組成部分
public class ResultMap { private Configuration configuration; // 唯一ID,一般是namespace+resultMap標(biāo)簽的ID private String id; // 對(duì)應(yīng)的Java類型 private Class<?> type; // resultMap標(biāo)簽中的result標(biāo)簽的集合 private List<ResultMapping> resultMappings; // resultMap標(biāo)簽中的id標(biāo)簽的集合 private List<ResultMapping> idResultMappings; // resultMap標(biāo)簽中的constructor標(biāo)簽的集合 private List<ResultMapping> constructorResultMappings; // resultMap標(biāo)簽中所有子標(biāo)簽的集合 private List<ResultMapping> propertyResultMappings; // 結(jié)果集中列名 private Set<String> mappedColumns; // 對(duì)應(yīng)的Java對(duì)象的屬性名 private Set<String> mappedProperties; // discriminator鑒別器會(huì)單獨(dú)被解析成該對(duì)象 private Discriminator discriminator; // 是否是嵌套映射 private boolean hasNestedResultMaps; // 是否嵌套查詢 private boolean hasNestedQueries; // 是否自動(dòng)映射 private Boolean autoMapping; }
其中比較重要的幾個(gè)屬性就是
- resultMappings:它表示resultMap標(biāo)簽中的result標(biāo)簽的集合(包含id標(biāo)簽)后續(xù)結(jié)果集的解析過(guò)程避免不了會(huì)遍歷它
- hasNestedResultMaps:是否由嵌套映射,一般都是resumtMap標(biāo)簽中含有collection標(biāo)簽association標(biāo)簽才會(huì)為true,后面介紹到嵌套映射會(huì)分析。
ResultHandler和ResultContext
在mybatis的解析過(guò)程中,我們很容易想象到它是遍歷結(jié)果集然后和resultMap標(biāo)簽的映射關(guān)系進(jìn)行比較,最終生成結(jié)果集對(duì)象。那么遍歷結(jié)果集其實(shí)就是對(duì)一條一條數(shù)據(jù)進(jìn)行單獨(dú)處理。處理完一條記錄,就把這條記錄對(duì)應(yīng)的對(duì)象添加到一個(gè)集合中,最終獲取這個(gè)集合就是用戶想要得到的對(duì)象。
ResultContext用來(lái)在上下文中傳遞解析過(guò)后的單個(gè)對(duì)象,ResultHandler內(nèi)部有一個(gè)List集合用來(lái)存儲(chǔ)單條數(shù)據(jù)解析后的對(duì)象的。他們的源碼非常簡(jiǎn)單易懂,首先我們來(lái)看DefaultResultHandler(ResultHandler的實(shí)現(xiàn)類)的源碼
public class DefaultResultHandler implements ResultHandler<Object> { private final List<Object> list; @Override public void handleResult(ResultContext<?> context) { list.add(context.getResultObject()); } public List<Object> getResultList() { return list; } }
它只有一個(gè)屬性list
,list
就是用來(lái)存儲(chǔ)解析單條數(shù)據(jù)后的對(duì)象。它還提供了兩個(gè)方法
- handleResult:就是把上下文中單條數(shù)據(jù)處理后的對(duì)象存入到list集合中。
- getResultList:獲取結(jié)果集解析過(guò)后的list,也就是用戶最終需要的list對(duì)象。
接下來(lái)來(lái)看下ResultContext這個(gè)上下文對(duì)象
public class DefaultResultContext<T> implements ResultContext<T> { private T resultObject; private int resultCount; private boolean stopped; public T getResultObject() { return resultObject; } public void nextResultObject(T resultObject) { resultCount++; this.resultObject = resultObject; } }
它由三個(gè)屬性:
- resultObject:用來(lái)保存當(dāng)前解析的這條記錄的結(jié)果
- resultCount:結(jié)果集的數(shù)量,每解析完一條記錄,該值加一
- stopped:是否停止解析,當(dāng)?shù)阶詈笠粭l記錄時(shí),該值為false
它的兩個(gè)方法比較簡(jiǎn)單,就不再說(shuō)了。
接下來(lái)我們正式進(jìn)入mybatis解析結(jié)果集的核心代碼分析。mybatis解析結(jié)果集可以分為兩大類:一是簡(jiǎn)單映射、而是嵌套映射。接下來(lái)我們即從這兩個(gè)方面研究
簡(jiǎn)單映射
接下來(lái)就來(lái)看下本文主題handleResultSet
方法的核心邏輯
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { if (parentMapping != null) { // 1. 處理resultSets標(biāo)簽的邏輯 handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { // 2. 正常處理resultMap標(biāo)簽。將結(jié)果集中的記錄映射為指定對(duì)象。可能涉及到嵌套映射。 if (resultHandler == null) { // 默認(rèn)都會(huì)走進(jìn)該分支。 // ResultSet是個(gè)集合嘛,那Result自然就是集合中的一條記錄啦。DefaultResultHandler是用來(lái)處理單條Result記錄的。 DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); // 處理多行記錄的方法(重要?。。。。。。。? handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { // 如果用戶自定義了 resultHandler 使用用戶自定義的 handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } }
這個(gè)方法簡(jiǎn)介明了,它的執(zhí)行流程是這樣的:
第一步:判斷parentMapping是否存在值。而這個(gè)分支是專門為解析resultSets
標(biāo)簽使用的分支,其實(shí)和普通的解析結(jié)果集沒什么不同。等看完后面的解析過(guò)程再回過(guò)頭看就理解了。這個(gè)分支不重要,我們繼續(xù)往下走
第二步:判斷用戶是否指定resultHandler
類型,一般都是不會(huì)指定的,所以他會(huì)走第一個(gè)分支。創(chuàng)建一個(gè)默認(rèn)的ResultHandler,然后執(zhí)行handleRowValues方法
第三步:如果用戶指定resultHandler
類型,就執(zhí)行handleRowValues方法
可以發(fā)現(xiàn)無(wú)論是哪個(gè)分支,最終都會(huì)走到handleRowValues方法,從名字也能看出,該方法的邏輯是處理多行數(shù)據(jù)。接下來(lái)我們就來(lái)看下DefaultResultSetHandler#handleRowValues方法
handleRowValues
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { if (resultMap.hasNestedResultMaps()) { ensureNoRowBounds(); checkResultHandler(); // 處理嵌套映射 handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } else { // 處理簡(jiǎn)單映射 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } }
該方法只做了一件事情,那就是判斷結(jié)果集是否是嵌套映射的。這取決于xml文件中resultMap
標(biāo)簽是否定義了collection
和association標(biāo)簽。
這里我們先討論簡(jiǎn)單映射的場(chǎng)景。所以接下來(lái)我們來(lái)看DefaultResultSetHandler#handleRowValuesForSimpleResultMap方法。從名字也可以看出,該方法的邏輯是處理簡(jiǎn)單映射。簡(jiǎn)單映射不會(huì)涉及延遲加載等復(fù)雜的邏輯,所以源碼很好理解
handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { // 第一步:經(jīng)過(guò)resultHandler處理過(guò)后,每行記錄會(huì)被映射成一個(gè)對(duì)象。該對(duì)象暫存在 這個(gè)Result上下文中。(暫存DefaultResultContext) DefaultResultContext<Object> resultContext = new DefaultResultContext<>(); ResultSet resultSet = rsw.getResultSet(); // 跳過(guò)指定的偏移量,相當(dāng)于limit只不過(guò)他是內(nèi)存分頁(yè),一般用不到(RowBounds中指定的偏移量) skipRows(resultSet, rowBounds); // 第二步:結(jié)果集中會(huì)有很多記錄,通過(guò)該循環(huán)處理結(jié)果集中的每條記錄。每個(gè)記錄最后都會(huì)存儲(chǔ)到ResultHandler的List集合中。 while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { // 處理discrimination標(biāo)簽。類似于Java中的switch語(yǔ)法。如果resultMap標(biāo)簽有鑒別器。則根據(jù)case的情況動(dòng)態(tài)的獲取resultMap映射結(jié)果集 ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); // 第三步:獲取記錄映射的對(duì)象(重要?。。。。。。? Object rowValue = getRowValue(rsw, discriminatedResultMap, null); // 第四步:把對(duì)象存儲(chǔ)到上下文中。 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } }
下面是該方法的執(zhí)行邏輯
第一步:定義ResultContext對(duì)象(前文說(shuō)過(guò)),它用來(lái)暫存每一行記錄的處理結(jié)果的對(duì)象。從ResultSetWrapper中獲取JDBC的結(jié)果集對(duì)象ResultSet。skipRows方法跳過(guò)指定的內(nèi)存分頁(yè)(一般沒什么用,忽略該方法即可,研究?jī)r(jià)值不大)
第二步:遍歷結(jié)果集,通過(guò)resolveDiscriminatedResultMap方法得到鑒別器鑒別的ResultMap對(duì)象,其實(shí)本質(zhì)上還是一個(gè)ResultMap(有關(guān)鑒別器的內(nèi)容請(qǐng)參考另一篇文章:MyBatis discriminator標(biāo)簽 實(shí)戰(zhàn)和原理解析
第三步:調(diào)用getRowValue方法處理每一行數(shù)據(jù)并得到結(jié)果對(duì)象
第四步:把上一步生成的對(duì)象通過(guò)上下文ResultContext添加到ResultHandler中。(再次提示:ResultHandler對(duì)象里面存放結(jié)果集解析后的對(duì)象集合哦)
其中,重點(diǎn)操作在第三步和第四步。接下來(lái)我們就先來(lái)聊聊getRowValue
方法是如何處理一行記錄的。
getRowValue
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // (重要方法?。。。。。。。。。。。。┍热绶祷亟Y(jié)果ResultType指定的是map,那么這里就會(huì)創(chuàng)建一個(gè)空Map對(duì)象,下面的if才是給map里添加元素。 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, false)) { // (重要方法?。。。。。。。。。。。。┦欠駪?yīng)該自動(dòng)映射。比如說(shuō)SQL返回結(jié)果集有10列,但是resultMap只配置了9個(gè)字段,剩下的那個(gè)字段,如果滿足返回列與映射列名稱相同,就會(huì)自動(dòng)進(jìn)行映射 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } // (重要方法?。。。。。。。。。。。。┨幚韗esultMap標(biāo)簽中的映射。把結(jié)果集根據(jù)resultMap中的映射關(guān)系生成最終的結(jié)果對(duì)象。屬性會(huì)被設(shè)置到metaObject中 foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; // isReturnInstanceForEmptyRow是否啟用。如果開啟則返回空對(duì)象,否則返回null。 // 注:這個(gè)空對(duì)象依賴于createResultObject方法創(chuàng)建出的對(duì)象是空還是null。 rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } return rowValue; }
該方法處理邏輯如下(延時(shí)加載的代碼請(qǐng)忽略,簡(jiǎn)單映射不會(huì)涉及到延時(shí)加載,這里之所以會(huì)出現(xiàn)ResultLoaderMap這樣延時(shí)加載相關(guān)的對(duì)象,是因?yàn)榍短子成浞椒ㄒ矔?huì)調(diào)用這個(gè)方法解析結(jié)果集。)
第一步:首先通過(guò)createResultObject對(duì)象創(chuàng)建一個(gè)空殼對(duì)象,啥是空殼對(duì)象呢?比如說(shuō)xml文件中的resultMap標(biāo)簽定義了返回值類型是User類型,那么這里就會(huì)創(chuàng)建一個(gè)User對(duì)象,但是屬性值全為空。
當(dāng)然createResultObject背后的邏輯其實(shí)很復(fù)雜,它會(huì)首先判斷用戶是否在resultMap標(biāo)簽中定義了構(gòu)造器標(biāo)簽,如果沒定義才會(huì)按上述流程描述所說(shuō)的創(chuàng)建一個(gè)空殼對(duì)象。但如果用戶在resultMap標(biāo)簽中定義了構(gòu)造器標(biāo)簽,那么就會(huì)直接將結(jié)果集中的數(shù)據(jù)通過(guò)構(gòu)造器構(gòu)造出完整的對(duì)象。這樣也很能理解嗎,畢竟調(diào)用構(gòu)造器是要傳值的。
第二步:通過(guò)applyAutomaticMappings方法完成自動(dòng)映射,啥是自動(dòng)映射嘞。比如一條sql返回的列由name age兩列,但是resultMap標(biāo)簽中只定義了一個(gè)映射關(guān)系name,那么age列就會(huì)自動(dòng)尋找映射關(guān)系,如果發(fā)現(xiàn)列名age與resultMap返回類型的屬性同名,就會(huì)自動(dòng)映射到該類型中。該步驟中只會(huì)映射resultMap標(biāo)簽中未定義的映射關(guān)系對(duì)應(yīng)的字段
第三步:通過(guò)applyPropertyMappings方法完成屬性映射,它是第二步的補(bǔ)充,完成resultMap標(biāo)簽中result標(biāo)簽的映射關(guān)系
最后:返回解析完成的值
到此,結(jié)果集中的一條記錄已經(jīng)被解析用戶指定的類型對(duì)象了,接下來(lái)就是要把該對(duì)象返回給用戶,這就要回到我們上一步的storeObject
方法了。接下來(lái)來(lái)看下DefaultResultSetHandler#storeObject
方法
storeObject
private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException { if (parentMapping != null) { // 處理ResultSets標(biāo)簽時(shí)走這個(gè)分支 linkToParents(rs, parentMapping, rowValue); } else { callResultHandler(resultHandler, resultContext, rowValue); } } private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) { resultContext.nextResultObject(rowValue); ((ResultHandler<Object>) resultHandler).handleResult(resultContext); }
storeObject方法的邏輯也很簡(jiǎn)單(一般都會(huì)走到callResultHandler方法)
第一步:通過(guò)nextResultObject方法存儲(chǔ)getRowValue方法解析的結(jié)果對(duì)象。
第二步:把單行記錄的解析對(duì)象存入到ResultHandler的集合當(dāng)中
嵌套映射
簡(jiǎn)單介紹完了簡(jiǎn)單映射之后,接下來(lái)我們來(lái)看下比較復(fù)雜的嵌套映射,在嵌套映射中也會(huì)有很多問(wèn)題,比如:循環(huán)依賴如何解決、延遲加載如何解決。 本節(jié)只針對(duì)于一般情況下的嵌套映射做出分析,循環(huán)依賴和延時(shí)加載后續(xù)我會(huì)繼續(xù)更新文章。
回想一下上一小節(jié)中提到的handleRowValues方法,它會(huì)根據(jù)ResultMap對(duì)象判斷是否是嵌套映射,如果是嵌套映射,就會(huì)走入handleRowValuesForNestedResultMap方法,接下來(lái)我們就來(lái)看下handleRowValuesForNestedResultMap這個(gè)方法。
handleRowValuesForNestedResultMap
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { // 第一步 獲取上下文對(duì)象,用于存儲(chǔ)單行處理后的記錄 final DefaultResultContext<Object> resultContext = new DefaultResultContext<>(); ResultSet resultSet = rsw.getResultSet(); skipRows(resultSet, rowBounds); Object rowValue = previousRowValue; // 第二步: 遍歷結(jié)果集 while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); // 嵌套映射中的結(jié)果都會(huì)被存入到nestedResultObjects中。這里的CacheKey就是緩存對(duì)象唯一標(biāo)識(shí) final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null); // 映射過(guò)程中的對(duì)象也會(huì)被存入在此。 Object partialObject = nestedResultObjects.get(rowKey); // 通常會(huì)走到該分支。獲取一行記錄處理后的對(duì)象(有可能是半成品) rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject); if (partialObject == null) { storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } } if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) { storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); previousRowValue = null; } else if (rowValue != null) { previousRowValue = rowValue; } }
其實(shí)仔細(xì)閱讀這段可以發(fā)現(xiàn)它和簡(jiǎn)單映射的代碼是有一部分雷同的,這也很好理解,嵌套映射還是基于簡(jiǎn)單映射的思想進(jìn)行遞歸處理結(jié)果集映射關(guān)系的嘛。接下來(lái)來(lái)介紹一下代碼的流程
第一步:還是獲取上下文對(duì)象,獲取JDBC的結(jié)果集對(duì)象。就不羅嗦了
第二步:遍歷結(jié)果集,處理每一行數(shù)據(jù)
第三步:創(chuàng)建CacheKey對(duì)象,使用嵌套映射中的外層對(duì)象創(chuàng)建緩存key,為了解決一對(duì)多映射
第四步:從緩存中通過(guò)CacheKey對(duì)象獲取外層對(duì)象。這里的nestedResultObjects是DefaultResultSetHandler的一個(gè)屬性,他就是用來(lái)緩存嵌套映射中的外層對(duì)象的。如果是第一次循環(huán),從緩存中獲取肯定是為空的嘛。但是你想一下這種場(chǎng)景——一個(gè)用戶(user)擁有多個(gè)訂單(order),這種一堆多的關(guān)系,在同一個(gè)用戶進(jìn)行到循環(huán)的第二輪時(shí),肯定不希望再創(chuàng)建一個(gè)外層對(duì)象,而是想讓order對(duì)象都公用一個(gè)外城對(duì)象。這種情況只能添加緩存來(lái)做了。這就是nestedResultObjects的意義。
這里的partialObject就是外層對(duì)象,也可以稱作是部分對(duì)象,因?yàn)樗煌暾铩?/p>
第五步:調(diào)用getRowValue方法獲取行記錄解析的結(jié)果
第六步:調(diào)用storeObject方法存儲(chǔ)對(duì)象到ResultHancler中
其中最重要的是要理解第四步中的嵌套思想,以及第五步解析一行記錄的詳細(xì)過(guò)程。這里一定會(huì)涉及到循環(huán)調(diào)用,因?yàn)榻馕鐾鈱訉?duì)象必然要解析內(nèi)層對(duì)象嘛
getRowValue
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException { final String resultMapId = resultMap.getId(); Object rowValue = partialObject; // 映射第一次肯定一定為null if (rowValue != null) { final MetaObject metaObject = configuration.newMetaObject(rowValue); putAncestor(rowValue, resultMapId); applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false); ancestorObjects.remove(resultMapId); } else { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, true)) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; putAncestor(rowValue, resultMapId); foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues; ancestorObjects.remove(resultMapId); foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } if (combinedKey != CacheKey.NULL_CACHE_KEY) { nestedResultObjects.put(combinedKey, rowValue); } } return rowValue; }
映射時(shí),第一次外層對(duì)象一定為null,所以會(huì)走else分支,只有在一堆多種的第二次映射才會(huì)走到if分支。我們來(lái)看下else分支的處理邏輯
第一步:首先通過(guò)createResultObject對(duì)象創(chuàng)建一個(gè)空殼對(duì)象,簡(jiǎn)單映射中提過(guò)
第二步:通過(guò)applyAutomaticMappings方法完成自動(dòng)映射
第三步:通過(guò)applyPropertyMappings方法完成屬性映射,它是第二步的補(bǔ)充,完成resultMap標(biāo)簽中result標(biāo)簽的映射關(guān)系
第四步:通過(guò)applyNestedResultMappings方法完成嵌套映射
到此,結(jié)果集中的一條記錄已經(jīng)被嵌套解析完成了。其中和簡(jiǎn)單映射不一樣的地方只有多調(diào)用了一個(gè)applyNestedResultMappings方法,完成嵌套映射。我們來(lái)看下這個(gè)方法
applyNestedResultMappings
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) { boolean foundValues = false; for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) { final String nestedResultMapId = resultMapping.getNestedResultMapId(); if (nestedResultMapId != null && resultMapping.getResultSet() == null) { // 獲取內(nèi)層對(duì)象 rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue); if (rowValue != null && !knownValue) { // 把內(nèi)層對(duì)象關(guān)聯(lián)到外層對(duì)象 linkObjects(metaObject, resultMapping, rowValue); foundValues = true; } } } return foundValues; }
它的執(zhí)行邏輯是遍歷resultMapping,對(duì)resultMapping進(jìn)行g(shù)etRowValue方法的調(diào)用,此時(shí)就應(yīng)該屬于簡(jiǎn)單影射了(除非嵌套了很多層)
它通過(guò)for循環(huán)每個(gè)resultMapping,直到找到嵌套映射的哪一個(gè)字段,然后再進(jìn)行簡(jiǎn)單映射封裝結(jié)果返回作為外層對(duì)象的屬性值。
它的調(diào)用邏輯是getRowValue——applyNestedResultMappings——getRowValue,直到嵌套映射完成位置,嵌套多少層,這個(gè)鏈路就會(huì)深多少層。
小結(jié)
mybatis解析結(jié)果集是通過(guò)遍歷結(jié)果集數(shù)據(jù),并把結(jié)果集數(shù)據(jù)對(duì)照resultMap標(biāo)簽中的映射關(guān)系一一映射,如果存在嵌套映射則嵌套執(zhí)行g(shù)etRowValue獲取一行記錄的結(jié)果并關(guān)聯(lián)到外層對(duì)象。
以上就是MyBatis handleResultSet結(jié)果集解析過(guò)程示例的詳細(xì)內(nèi)容,更多關(guān)于MyBatis handleResultSet 的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JAVA StringBuffer類與StringTokenizer類代碼解析
這篇文章主要介紹了JAVA StringBuffer類與StringTokenizer類代碼解析,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01Java POI讀取excel中數(shù)值精度損失問(wèn)題解決
這篇文章主要介紹了Java POI讀取excel中數(shù)值精度損失問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04解決springcloud阿里云OSS文件訪問(wèn)跨域問(wèn)題的實(shí)現(xiàn)
本文主要介紹了解決springcloud阿里云OSS文件訪問(wèn)跨域問(wèn)題的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06SpringMVC MVC架構(gòu)原理及實(shí)現(xiàn)方法詳解
這篇文章主要介紹了SpringMVC MVC架構(gòu)原理及實(shí)現(xiàn)方法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09使用Java實(shí)現(xiàn)將ppt轉(zhuǎn)換為文本
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)將ppt轉(zhuǎn)換為文本,文中的示例代碼簡(jiǎn)潔易懂,具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下2024-01-01Spring使用AOP完成統(tǒng)一結(jié)果封裝實(shí)例demo
這篇文章主要介紹了Spring使用AOP完成統(tǒng)一結(jié)果封裝,本文通過(guò)實(shí)現(xiàn)demo給大家詳細(xì)講解,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02