mybatis?collection和association的區(qū)別解析
1.collection標簽
說到mybatis的collection標簽,我們肯定不陌生,可以通過它解決一對多的映射問題,舉個例子一個用戶對應(yīng)多個系統(tǒng)權(quán)限,通過對用戶表和權(quán)限表的關(guān)聯(lián)查詢我們可以得到好多條記錄,但是用戶信息這部分在多條記錄中是重復(fù)的,只有權(quán)限不同,我們需要把這多條權(quán)限記錄映射到這個用戶之中,這個時候可以通過collection標簽/association標簽來解決(雖然assocation標簽一般是解決一對一問題的,但它實際上也能實現(xiàn)我們的需求,可以通過后面的源碼看出來)
1.1 相關(guān)代碼和運行結(jié)果
實體類和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);
}
}
}運行結(jié)果如下,可以看到權(quán)限記錄映射到屬性permitDOList 的list列表了

1.2 collection部分源碼解析
通過PreparedStatement查詢完之后得到ResultSet結(jié)果集,之后需要將結(jié)果集解析為java的pojo類中,下面通過源碼簡單講下是如何解析的
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 {
// 無嵌套
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}根據(jù)有無嵌套分成兩層邏輯,有嵌套resultMaps就是指<resultMap>標簽下有子標簽<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();
// 跳過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);
// 部分對象(可能存在對象內(nèi)容缺失未完全合并)
Object partialObject = nestedResultObjects.get(rowKey);
// issue #577 && #542
// 關(guān)于resultOrdered的理解,舉例若查詢得到四條記錄a,a,b,a , 相同可以合并。
// 那么當resultOrdered=true時,最后可以得到三條記錄,第一條和第二條合并成一條、第三條單獨一條、第四條也是單獨一條記錄
// resultOrdered=false時,最后可以得到兩條記錄,第一條、第二條和第四條會合并成一條,第三條單獨一條記錄
// 另外存儲到resultHandler的時機也不一樣,resultOrdered=true是等遇到不可合并的記錄的時候才把之前已經(jīng)合并的記錄存儲,
// 而resultOrdered=false是直接存儲的后續(xù)有合并的記錄再處理添加到集合屬性中
if (mappedStatement.isResultOrdered()) {
// partialObject為null,說明這一條記錄不可與上一條記錄進行合并了,那么清空nestedResultObjects防止之后出現(xiàn)有可合并的記錄的時候繼續(xù)合并
// 然后將記錄存儲到resultHandler里面
if (partialObject == null && rowValue != null) {
nestedResultObjects.clear();
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
} else {
// 處理resultSet的當前這一條記錄
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
if (partialObject == null) {
// 將記錄存儲到resultHandler里面
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
}這段代碼主要是創(chuàng)建了一個緩存key,主要是根據(jù)resultMapId和<id>標簽的column和對應(yīng)的columvalue來創(chuàng)建的(若沒有<id>標簽,則會使用所有的<result>標簽的column和columnValue來創(chuàng)建),以此緩存鍵來區(qū)分記錄是否可合并。nestedResultObjects是一個儲存結(jié)果的map,以緩存鍵為key,實體類(本例中為UserDO)為value,若能以cacheKey取到值,則說明本條記錄可合并。
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時,說明此條記錄可合并
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接收對象,本例中是UserDO對象
rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
// 是否將查詢出來的字段全部映射 默認false
if (shouldApplyAutomaticMappings(resultMap, true)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 設(shè)置需要映射的屬性值,不管有嵌套ResultMap的
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解析為實體類對象,applyPropertyMappings填充<id><result>標簽的實體屬性值
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)過一次鑒權(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)建過
Object rowValue = nestedResultObjects.get(combinedKey);
boolean knownValue = rowValue != null;
// 實例化集合屬性 list復(fù)制為空列表
instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
// 存在指定的非空列存在空值則返回false
if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
if (rowValue != null && !knownValue) {
// 合并記錄,設(shè)置對象-association或?qū)ο筇砑拥郊蠈傩灾?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é)果映射,其實就是<collection><association>標簽。同時調(diào)用getRowValue方法根據(jù)<collection>指定的resultMap獲取實體對象(這里是PermitDO對象),然后調(diào)用linkObjects方法將permitDO對象調(diào)用add方法添加到permitDOList中
private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
// 屬性是集合進行添加 <collection>
if (collectionProperty != null) {
final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
targetMetaObject.add(rowValue);
} else {
// 否則是對象 直接進行setter設(shè)置 <association>
metaObject.setValue(resultMapping.getProperty(), rowValue);
}
}最后就把能合并的記錄都合并在一起了,不同的權(quán)限映射到permitDOList這個集合中了
1.3 <collection>和<association>的相同的和不同點
從上面的代碼看來,關(guān)于<collection>和<association>標簽都屬于嵌套結(jié)果集了,處理邏輯也是基本相同的沒啥區(qū)分,換句話來說,把上面的<collection>替換成<association>標簽其實也能得到相同的結(jié)果,關(guān)鍵還是pojo類中javaType的屬性,若屬性為List則會創(chuàng)建空的list并將嵌套結(jié)果映射添加到list中(即使是一對一的那么list中就只有一條記錄),若屬性為普通對象則直接進行setter設(shè)置。

從上面的圖中我們可以看到<collection>和<association>標簽屬性基本相同,<collection>比<association>多了一個ofType屬性,這個ofType屬性其實就是collection集合中單個元素的javaType屬性,<collection>的javaType屬性是繼承了Collection接口的list或set等java集合屬性。
另外在使用習(xí)慣上因為我們能確認表和表之間的關(guān)系是一對一還是一對多的,能夠確認pojo類中的屬性javaType是使用list還是普通對象,所以一般情況下一對一使用<association>標簽,一對多使用<collection>標簽,語義上更清晰更好理解。
最后
如果說的有問題歡迎提出指正討論,代碼提交在gitee上,感興趣的同學(xué)可以下載看看
到此這篇關(guān)于mybatis collection解析以及和association的區(qū)別的文章就介紹到這了,更多相關(guān)mybatis collection內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- mybatis collection關(guān)聯(lián)查詢多個參數(shù)方式
- MyBatis使用嵌套查詢collection和association的實現(xiàn)
- mybatis?resultMap之collection聚集兩種實現(xiàn)方式
- mybatis中association和collection的使用與區(qū)別
- MyBatis的collection和association的使用解讀
- Mybatis中一對多(collection)和一對一(association)的組合查詢使用
- Mybatis使用Collection屬性的示例代碼
- Mybatis的collection三層嵌套查詢方式(驗證通過)
- MyBatis中<collection>標簽的多種用法
相關(guān)文章
百度翻譯API使用詳細教程(前端vue+后端springboot)
這篇文章主要給大家介紹了關(guān)于百度翻譯API使用的相關(guān)資料,百度翻譯API是百度面向開發(fā)者推出的免費翻譯服務(wù)開放接口,任何第三方應(yīng)用或網(wǎng)站都可以通過使用百度翻譯API為用戶提供實時優(yōu)質(zhì)的多語言翻譯服務(wù),需要的朋友可以參考下2024-02-02
Java實現(xiàn)利用廣度優(yōu)先遍歷(BFS)計算最短路徑的方法
這篇文章主要介紹了Java實現(xiàn)利用廣度優(yōu)先遍歷(BFS)計算最短路徑的方法,實例分析了廣度優(yōu)先遍歷算法的原理與使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-04-04
Java如何使用ReentrantLock實現(xiàn)長輪詢
這篇文章主要介紹了如何使用ReentrantLock實現(xiàn)長輪詢,對ReentrantLock感興趣的同學(xué),可以參考下2021-04-04
springboot jdbctemplate如何實現(xiàn)多數(shù)據(jù)源
這篇文章主要介紹了springboot jdbctemplate如何實現(xiàn)多數(shù)據(jù)源問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07

