mybatis?collection和association的區(qū)別解析
1.collection標(biāo)簽
說(shuō)到mybatis的collection標(biāo)簽,我們肯定不陌生,可以通過(guò)它解決一對(duì)多的映射問(wèn)題,舉個(gè)例子一個(gè)用戶(hù)對(duì)應(yīng)多個(gè)系統(tǒng)權(quán)限,通過(guò)對(duì)用戶(hù)表和權(quán)限表的關(guān)聯(lián)查詢(xún)我們可以得到好多條記錄,但是用戶(hù)信息這部分在多條記錄中是重復(fù)的,只有權(quán)限不同,我們需要把這多條權(quán)限記錄映射到這個(gè)用戶(hù)之中,這個(gè)時(shí)候可以通過(guò)collection標(biāo)簽/association標(biāo)簽來(lái)解決(雖然assocation標(biāo)簽一般是解決一對(duì)一問(wèn)題的,但它實(shí)際上也能實(shí)現(xiàn)我們的需求,可以通過(guò)后面的源碼看出來(lái))
1.1 相關(guān)代碼和運(yùn)行結(jié)果
實(shí)體類(lèi)和mapper代碼
public class Test { public static void main(String[] args) throws IOException { try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) { // 構(gòu)建session工廠 DefaultSqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserDO userDO = userMapper.getByUserId(1); System.out.println(userDO); } } }
運(yùn)行結(jié)果如下,可以看到權(quán)限記錄映射到屬性permitDOList 的list列表了
1.2 collection部分源碼解析
通過(guò)PreparedStatement查詢(xún)完之后得到ResultSet結(jié)果集,之后需要將結(jié)果集解析為java的pojo類(lèi)中,下面通過(guò)源碼簡(jiǎn)單講下是如何解析的
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { // 是否有嵌套的resultMaps if (resultMap.hasNestedResultMaps()) { ensureNoRowBounds(); checkResultHandler(); handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } else { // 無(wú)嵌套 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } }
根據(jù)有無(wú)嵌套分成兩層邏輯,有嵌套resultMaps就是指<resultMap>標(biāo)簽下有子標(biāo)簽<collection>或<association>,分析下第一層
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(); // 跳過(guò)offset行 skipRows(resultSet, rowBounds); // 上一次獲取的數(shù)據(jù) Object rowValue = previousRowValue; // 已獲取記錄數(shù)量小于limit while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { // 鑒別器解析 final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); // 創(chuàng)建緩存key resultMapId + (columnName + columnValue).... final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null); // 部分對(duì)象(可能存在對(duì)象內(nèi)容缺失未完全合并) Object partialObject = nestedResultObjects.get(rowKey); // issue #577 && #542 // 關(guān)于resultOrdered的理解,舉例若查詢(xún)得到四條記錄a,a,b,a , 相同可以合并。 // 那么當(dāng)resultOrdered=true時(shí),最后可以得到三條記錄,第一條和第二條合并成一條、第三條單獨(dú)一條、第四條也是單獨(dú)一條記錄 // resultOrdered=false時(shí),最后可以得到兩條記錄,第一條、第二條和第四條會(huì)合并成一條,第三條單獨(dú)一條記錄 // 另外存儲(chǔ)到resultHandler的時(shí)機(jī)也不一樣,resultOrdered=true是等遇到不可合并的記錄的時(shí)候才把之前已經(jīng)合并的記錄存儲(chǔ), // 而resultOrdered=false是直接存儲(chǔ)的后續(xù)有合并的記錄再處理添加到集合屬性中 if (mappedStatement.isResultOrdered()) { // partialObject為null,說(shuō)明這一條記錄不可與上一條記錄進(jìn)行合并了,那么清空nestedResultObjects防止之后出現(xiàn)有可合并的記錄的時(shí)候繼續(xù)合并 // 然后將記錄存儲(chǔ)到resultHandler里面 if (partialObject == null && rowValue != null) { nestedResultObjects.clear(); storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject); } else { // 處理resultSet的當(dāng)前這一條記錄 rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject); if (partialObject == null) { // 將記錄存儲(chǔ)到resultHandler里面 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } } }
這段代碼主要是創(chuàng)建了一個(gè)緩存key,主要是根據(jù)resultMapId和<id>標(biāo)簽的column和對(duì)應(yīng)的columvalue來(lái)創(chuàng)建的(若沒(méi)有<id>標(biāo)簽,則會(huì)使用所有的<result>標(biāo)簽的column和columnValue來(lái)創(chuàng)建),以此緩存鍵來(lái)區(qū)分記錄是否可合并。nestedResultObjects是一個(gè)儲(chǔ)存結(jié)果的map,以緩存鍵為key,實(shí)體類(lèi)(本例中為UserDO)為value,若能以cacheKey取到值,則說(shuō)明本條記錄可合并。
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException { final String resultMapId = resultMap.getId(); Object rowValue = partialObject; // rowValue不等于null時(shí),說(shuō)明此條記錄可合并 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(); // 創(chuàng)建result接收對(duì)象,本例中是UserDO對(duì)象 rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; // 是否將查詢(xún)出來(lái)的字段全部映射 默認(rèn)false if (shouldApplyAutomaticMappings(resultMap, true)) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } // 設(shè)置需要映射的屬性值,不管有嵌套R(shí)esultMap的 foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; // 存放第一條數(shù)據(jù) putAncestor(rowValue, resultMapId); // 處理有嵌套的resultMapping foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues; ancestorObjects.remove(resultMapId); foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } // 將最終結(jié)果放入到nestedResultObjects中 if (combinedKey != CacheKey.NULL_CACHE_KEY) { nestedResultObjects.put(combinedKey, rowValue); } } return rowValue; }
getRowValue方法主要是將ResultSet解析為實(shí)體類(lèi)對(duì)象,applyPropertyMappings填充<id><result>標(biāo)簽的實(shí)體屬性值
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) { boolean foundValues = false; for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) { // 嵌套id final String nestedResultMapId = resultMapping.getNestedResultMapId(); // resultMapping有嵌套的map才繼續(xù) <association> <collection> if (nestedResultMapId != null && resultMapping.getResultSet() == null) { try { final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping); // 獲取嵌套(經(jīng)過(guò)一次鑒權(quán))的ResultMap final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix); if (resultMapping.getColumnPrefix() == null) { // try to fill circular reference only when columnPrefix // is not specified for the nested result map (issue #215) Object ancestorObject = ancestorObjects.get(nestedResultMapId); if (ancestorObject != null) { if (newObject) { linkObjects(metaObject, resultMapping, ancestorObject); // issue #385 } continue; } } // 構(gòu)建嵌套map的key final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix); // 合并cacheKey final CacheKey combinedKey = combineKeys(rowKey, parentRowKey); // 嘗試獲取之前是否已經(jīng)創(chuàng)建過(guò) Object rowValue = nestedResultObjects.get(combinedKey); boolean knownValue = rowValue != null; // 實(shí)例化集合屬性 list復(fù)制為空列表 instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory // 存在指定的非空列存在空值則返回false if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) { rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue); if (rowValue != null && !knownValue) { // 合并記錄,設(shè)置對(duì)象-association或?qū)?duì)象添加到集合屬性中-collection linkObjects(metaObject, resultMapping, rowValue); foundValues = true; } } } catch (SQLException e) { throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e); } } } return foundValues; }
處理嵌套的結(jié)果映射,其實(shí)就是<collection><association>標(biāo)簽。同時(shí)調(diào)用getRowValue方法根據(jù)<collection>指定的resultMap獲取實(shí)體對(duì)象(這里是PermitDO對(duì)象),然后調(diào)用linkObjects方法將permitDO對(duì)象調(diào)用add方法添加到permitDOList中
private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) { final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // 屬性是集合進(jìn)行添加 <collection> if (collectionProperty != null) { final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty); targetMetaObject.add(rowValue); } else { // 否則是對(duì)象 直接進(jìn)行setter設(shè)置 <association> metaObject.setValue(resultMapping.getProperty(), rowValue); } }
最后就把能合并的記錄都合并在一起了,不同的權(quán)限映射到permitDOList這個(gè)集合中了
1.3 <collection>和<association>的相同的和不同點(diǎn)
從上面的代碼看來(lái),關(guān)于<collection>和<association>標(biāo)簽都屬于嵌套結(jié)果集了,處理邏輯也是基本相同的沒(méi)啥區(qū)分,換句話來(lái)說(shuō),把上面的<collection>替換成<association>標(biāo)簽其實(shí)也能得到相同的結(jié)果,關(guān)鍵還是pojo類(lèi)中javaType的屬性,若屬性為L(zhǎng)ist則會(huì)創(chuàng)建空的list并將嵌套結(jié)果映射添加到list中(即使是一對(duì)一的那么list中就只有一條記錄),若屬性為普通對(duì)象則直接進(jìn)行setter設(shè)置。
從上面的圖中我們可以看到<collection>和<association>標(biāo)簽屬性基本相同,<collection>比<association>多了一個(gè)ofType屬性,這個(gè)ofType屬性其實(shí)就是collection集合中單個(gè)元素的javaType屬性,<collection>的javaType屬性是繼承了Collection接口的list或set等java集合屬性。
另外在使用習(xí)慣上因?yàn)槲覀兡艽_認(rèn)表和表之間的關(guān)系是一對(duì)一還是一對(duì)多的,能夠確認(rèn)pojo類(lèi)中的屬性javaType是使用list還是普通對(duì)象,所以一般情況下一對(duì)一使用<association>標(biāo)簽,一對(duì)多使用<collection>標(biāo)簽,語(yǔ)義上更清晰更好理解。
最后
如果說(shuō)的有問(wèn)題歡迎提出指正討論,代碼提交在gitee上,感興趣的同學(xué)可以下載看看
到此這篇關(guān)于mybatis collection解析以及和association的區(qū)別的文章就介紹到這了,更多相關(guān)mybatis collection內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- mybatis collection關(guān)聯(lián)查詢(xún)多個(gè)參數(shù)方式
- MyBatis使用嵌套查詢(xún)collection和association的實(shí)現(xiàn)
- mybatis?resultMap之collection聚集兩種實(shí)現(xiàn)方式
- mybatis中association和collection的使用與區(qū)別
- MyBatis的collection和association的使用解讀
- Mybatis中一對(duì)多(collection)和一對(duì)一(association)的組合查詢(xún)使用
- Mybatis使用Collection屬性的示例代碼
- Mybatis的collection三層嵌套查詢(xún)方式(驗(yàn)證通過(guò))
- MyBatis中<collection>標(biāo)簽的多種用法
相關(guān)文章
Java實(shí)現(xiàn)synchronized鎖同步機(jī)制
synchronized是java內(nèi)置的同步鎖實(shí)現(xiàn),本文就詳細(xì)的介紹一下Java實(shí)現(xiàn)synchronized鎖同步機(jī)制,具有一定的參考價(jià)值,感興趣的可以了解一下2021-11-11Java調(diào)用Python代碼的幾種方法小結(jié)
Python語(yǔ)言有豐富的系統(tǒng)管理、數(shù)據(jù)處理、統(tǒng)計(jì)類(lèi)軟件包,因此從java應(yīng)用中調(diào)用Python代碼的需求很常見(jiàn)、實(shí)用,本文介紹幾種方法從java調(diào)用Python代碼,從而最大化利用兩個(gè)語(yǔ)言的優(yōu)勢(shì),需要的朋友可以參考下2025-01-01Java 代碼實(shí)例解析設(shè)計(jì)模式之監(jiān)聽(tīng)者模式
所謂監(jiān)聽(tīng)者模式,我理解的是構(gòu)建一個(gè)容器存放所有被監(jiān)聽(tīng)的線程或?qū)ο螅O(jiān)聽(tīng)每個(gè)線程或?qū)ο蟀l(fā)生的變化,若某個(gè)線程或?qū)ο笥|發(fā)指定規(guī)則,那么則對(duì)所有被監(jiān)聽(tīng)的線程或?qū)ο蟾鶕?jù)業(yè)務(wù)需要做處理2021-10-10在CentOS系統(tǒng)上安裝Java?JDK?8簡(jiǎn)單步驟
最近購(gòu)買(mǎi)一臺(tái)新的云服務(wù)器,用于開(kāi)發(fā)學(xué)習(xí)使用,因此需要安裝很多的組件,下面這篇文章主要給大家介紹了關(guān)于在CentOS系統(tǒng)上安裝Java?JDK8的簡(jiǎn)單步驟,需要的朋友可以參考下2023-12-12Java實(shí)現(xiàn)簡(jiǎn)單客戶(hù)信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)單客戶(hù)信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05Java?數(shù)據(jù)結(jié)構(gòu)與算法系列精講之單向鏈表
單向鏈表特點(diǎn)是鏈表的鏈接方向是單向的,訪問(wèn)要通過(guò)順序讀取從頭部開(kāi)始。鏈表是使用指針構(gòu)造的列表,是由一個(gè)個(gè)結(jié)點(diǎn)組裝起來(lái)的,又稱(chēng)為結(jié)點(diǎn)列表。其中每個(gè)結(jié)點(diǎn)都有指針成員變量指向列表中的下一個(gè)結(jié)點(diǎn),head指針指向第一個(gè)結(jié)點(diǎn)稱(chēng)為表頭,而終止于最后一個(gè)指向nuLL的指針2022-02-02