MyBatis?handleResultSet結(jié)果集解析過程示例
前置知識(shí)
mybatis版本: 3.5.12
之前說到mybatis執(zhí)行完SQL后的結(jié)果集是由DefaultResultHandler組件的handleResultSets處理的。今天就來探討下這個(gè)重要的方法,該方法非常核心,而且內(nèi)容比較多,所以單拎出來一章。而上文DefaultResultHandler處理結(jié)果集的大致流程請(qǐng)參考:MyBatis ResultSetHandler 結(jié)果集的解析過程
在理解handleResultSets是怎么對(duì)結(jié)果集進(jìn)行處理時(shí),需要明白MyBatis中這么幾個(gè)組件
- ResultMap和ResultMapping
- ResultHandler和ResultContext
- ResultLoaderMap和ResultLoader(這些是延時(shí)加載相關(guān))
- ProxyFactory(延時(shí)加載相關(guān),用來為延時(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)簽)。
接下來我們來看下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é)果集的解析過程避免不了會(huì)遍歷它
- hasNestedResultMaps:是否由嵌套映射,一般都是resumtMap標(biāo)簽中含有collection標(biāo)簽association標(biāo)簽才會(huì)為true,后面介紹到嵌套映射會(huì)分析。
ResultHandler和ResultContext
在mybatis的解析過程中,我們很容易想象到它是遍歷結(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用來在上下文中傳遞解析過后的單個(gè)對(duì)象,ResultHandler內(nèi)部有一個(gè)List集合用來存儲(chǔ)單條數(shù)據(jù)解析后的對(duì)象的。他們的源碼非常簡單易懂,首先我們來看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就是用來存儲(chǔ)解析單條數(shù)據(jù)后的對(duì)象。它還提供了兩個(gè)方法
- handleResult:就是把上下文中單條數(shù)據(jù)處理后的對(duì)象存入到list集合中。
- getResultList:獲取結(jié)果集解析過后的list,也就是用戶最終需要的list對(duì)象。
接下來來看下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:用來保存當(dāng)前解析的這條記錄的結(jié)果
- resultCount:結(jié)果集的數(shù)量,每解析完一條記錄,該值加一
- stopped:是否停止解析,當(dāng)?shù)阶詈笠粭l記錄時(shí),該值為false
它的兩個(gè)方法比較簡單,就不再說了。
接下來我們正式進(jìn)入mybatis解析結(jié)果集的核心代碼分析。mybatis解析結(jié)果集可以分為兩大類:一是簡單映射、而是嵌套映射。接下來我們即從這兩個(gè)方面研究
簡單映射
接下來就來看下本文主題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是用來處理單條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è)方法簡介明了,它的執(zhí)行流程是這樣的:
第一步:判斷parentMapping是否存在值。而這個(gè)分支是專門為解析resultSets標(biāo)簽使用的分支,其實(shí)和普通的解析結(jié)果集沒什么不同。等看完后面的解析過程再回過頭看就理解了。這個(gè)分支不重要,我們繼續(xù)往下走
第二步:判斷用戶是否指定resultHandler類型,一般都是不會(huì)指定的,所以他會(huì)走第一個(gè)分支。創(chuàng)建一個(gè)默認(rèn)的ResultHandler,然后執(zhí)行handleRowValues方法
第三步:如果用戶指定resultHandler類型,就執(zhí)行handleRowValues方法
可以發(fā)現(xiàn)無論是哪個(gè)分支,最終都會(huì)走到handleRowValues方法,從名字也能看出,該方法的邏輯是處理多行數(shù)據(jù)。接下來我們就來看下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 {
// 處理簡單映射
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
該方法只做了一件事情,那就是判斷結(jié)果集是否是嵌套映射的。這取決于xml文件中resultMap標(biāo)簽是否定義了collection和association標(biāo)簽。這里我們先討論簡單映射的場(chǎng)景。所以接下來我們來看DefaultResultSetHandler#handleRowValuesForSimpleResultMap方法。從名字也可以看出,該方法的邏輯是處理簡單映射。簡單映射不會(huì)涉及延遲加載等復(fù)雜的邏輯,所以源碼很好理解
handleRowValuesForSimpleResultMap
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
// 第一步:經(jīng)過resultHandler處理過后,每行記錄會(huì)被映射成一個(gè)對(duì)象。該對(duì)象暫存在 這個(gè)Result上下文中。(暫存DefaultResultContext)
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
// 跳過指定的偏移量,相當(dāng)于limit只不過他是內(nèi)存分頁,一般用不到(RowBounds中指定的偏移量)
skipRows(resultSet, rowBounds);
// 第二步:結(jié)果集中會(huì)有很多記錄,通過該循環(huán)處理結(jié)果集中的每條記錄。每個(gè)記錄最后都會(huì)存儲(chǔ)到ResultHandler的List集合中。
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 處理discrimination標(biāo)簽。類似于Java中的switch語法。如果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ì)象(前文說過),它用來暫存每一行記錄的處理結(jié)果的對(duì)象。從ResultSetWrapper中獲取JDBC的結(jié)果集對(duì)象ResultSet。skipRows方法跳過指定的內(nèi)存分頁(一般沒什么用,忽略該方法即可,研究價(jià)值不大)
第二步:遍歷結(jié)果集,通過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ì)象通過上下文ResultContext添加到ResultHandler中。(再次提示:ResultHandler對(duì)象里面存放結(jié)果集解析后的對(duì)象集合哦)
其中,重點(diǎn)操作在第三步和第四步。接下來我們就先來聊聊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)映射。比如說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)忽略,簡單映射不會(huì)涉及到延時(shí)加載,這里之所以會(huì)出現(xiàn)ResultLoaderMap這樣延時(shí)加載相關(guān)的對(duì)象,是因?yàn)榍短子成浞椒ㄒ矔?huì)調(diào)用這個(gè)方法解析結(jié)果集。)
第一步:首先通過createResultObject對(duì)象創(chuàng)建一個(gè)空殼對(duì)象,啥是空殼對(duì)象呢?比如說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ì)按上述流程描述所說的創(chuàng)建一個(gè)空殼對(duì)象。但如果用戶在resultMap標(biāo)簽中定義了構(gòu)造器標(biāo)簽,那么就會(huì)直接將結(jié)果集中的數(shù)據(jù)通過構(gòu)造器構(gòu)造出完整的對(duì)象。這樣也很能理解嗎,畢竟調(diào)用構(gòu)造器是要傳值的。
第二步:通過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)的字段
第三步:通過applyPropertyMappings方法完成屬性映射,它是第二步的補(bǔ)充,完成resultMap標(biāo)簽中result標(biāo)簽的映射關(guān)系
最后:返回解析完成的值
到此,結(jié)果集中的一條記錄已經(jīng)被解析用戶指定的類型對(duì)象了,接下來就是要把該對(duì)象返回給用戶,這就要回到我們上一步的storeObject方法了。接下來來看下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方法的邏輯也很簡單(一般都會(huì)走到callResultHandler方法)
第一步:通過nextResultObject方法存儲(chǔ)getRowValue方法解析的結(jié)果對(duì)象。
第二步:把單行記錄的解析對(duì)象存入到ResultHandler的集合當(dāng)中
嵌套映射
簡單介紹完了簡單映射之后,接下來我們來看下比較復(fù)雜的嵌套映射,在嵌套映射中也會(huì)有很多問題,比如:循環(huán)依賴如何解決、延遲加載如何解決。 本節(jié)只針對(duì)于一般情況下的嵌套映射做出分析,循環(huán)依賴和延時(shí)加載后續(xù)我會(huì)繼續(xù)更新文章。
回想一下上一小節(jié)中提到的handleRowValues方法,它會(huì)根據(jù)ResultMap對(duì)象判斷是否是嵌套映射,如果是嵌套映射,就會(huì)走入handleRowValuesForNestedResultMap方法,接下來我們就來看下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);
// 映射過程中的對(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)它和簡單映射的代碼是有一部分雷同的,這也很好理解,嵌套映射還是基于簡單映射的思想進(jìn)行遞歸處理結(jié)果集映射關(guān)系的嘛。接下來來介紹一下代碼的流程
第一步:還是獲取上下文對(duì)象,獲取JDBC的結(jié)果集對(duì)象。就不羅嗦了
第二步:遍歷結(jié)果集,處理每一行數(shù)據(jù)
第三步:創(chuàng)建CacheKey對(duì)象,使用嵌套映射中的外層對(duì)象創(chuàng)建緩存key,為了解決一對(duì)多映射
第四步:從緩存中通過CacheKey對(duì)象獲取外層對(duì)象。這里的nestedResultObjects是DefaultResultSetHandler的一個(gè)屬性,他就是用來緩存嵌套映射中的外層對(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ì)象。這種情況只能添加緩存來做了。這就是nestedResultObjects的意義。
這里的partialObject就是外層對(duì)象,也可以稱作是部分對(duì)象,因?yàn)樗煌暾铩?/p>
第五步:調(diào)用getRowValue方法獲取行記錄解析的結(jié)果
第六步:調(diào)用storeObject方法存儲(chǔ)對(duì)象到ResultHancler中
其中最重要的是要理解第四步中的嵌套思想,以及第五步解析一行記錄的詳細(xì)過程。這里一定會(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分支。我們來看下else分支的處理邏輯
第一步:首先通過createResultObject對(duì)象創(chuàng)建一個(gè)空殼對(duì)象,簡單映射中提過
第二步:通過applyAutomaticMappings方法完成自動(dòng)映射
第三步:通過applyPropertyMappings方法完成屬性映射,它是第二步的補(bǔ)充,完成resultMap標(biāo)簽中result標(biāo)簽的映射關(guān)系
第四步:通過applyNestedResultMappings方法完成嵌套映射
到此,結(jié)果集中的一條記錄已經(jīng)被嵌套解析完成了。其中和簡單映射不一樣的地方只有多調(diào)用了一個(gè)applyNestedResultMappings方法,完成嵌套映射。我們來看下這個(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)該屬于簡單影射了(除非嵌套了很多層)
它通過for循環(huán)每個(gè)resultMapping,直到找到嵌套映射的哪一個(gè)字段,然后再進(jìn)行簡單映射封裝結(jié)果返回作為外層對(duì)象的屬性值。
它的調(diào)用邏輯是getRowValue——applyNestedResultMappings——getRowValue,直到嵌套映射完成位置,嵌套多少層,這個(gè)鏈路就會(huì)深多少層。
小結(jié)
mybatis解析結(jié)果集是通過遍歷結(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é)果集解析過程示例的詳細(xì)內(nèi)容,更多關(guān)于MyBatis handleResultSet 的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JAVA StringBuffer類與StringTokenizer類代碼解析
這篇文章主要介紹了JAVA StringBuffer類與StringTokenizer類代碼解析,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01
Java POI讀取excel中數(shù)值精度損失問題解決
這篇文章主要介紹了Java POI讀取excel中數(shù)值精度損失問題解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
解決springcloud阿里云OSS文件訪問跨域問題的實(shí)現(xiàn)
本文主要介紹了解決springcloud阿里云OSS文件訪問跨域問題的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
SpringMVC MVC架構(gòu)原理及實(shí)現(xiàn)方法詳解
這篇文章主要介紹了SpringMVC MVC架構(gòu)原理及實(shí)現(xiàn)方法詳解,文中通過示例代碼介紹的非常詳細(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à)值,有需要的小伙伴可以參考下2024-01-01
Spring使用AOP完成統(tǒng)一結(jié)果封裝實(shí)例demo
這篇文章主要介紹了Spring使用AOP完成統(tǒng)一結(jié)果封裝,本文通過實(shí)現(xiàn)demo給大家詳細(xì)講解,代碼簡單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02

