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