MyBatis的MapKey注解實例解析
使用
mybatis中有很多實用的注解,但是平時想不起來使用。今天就來講一下MapKey是如何使用的
說明:本文基于mybatis原生框架3.3.0-SNAPSHOT
一、數(shù)據(jù)準備
數(shù)據(jù)庫準備一張user表,插入一點測試數(shù)據(jù)
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(20) DEFAULT NULL, `password` varchar(20) DEFAULT NULL, `age` int(11) DEFAULT NULL, `birthday` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1018 DEFAULT CHARSET=utf8mb4
select * from user;

二、Mapper配置
UserMapper接口
public interface UserMapper {
@Select("select * from user limit 1")
List<User> queryAll1();
@MapKey("username")
@Select("select * from user limit 1")
Map<String, User> queryAll2();
}
這里我們的mapper接口只有兩個方法queryAll1 queryAll2。這兩個方法執(zhí)行的SQL是一樣的,SQL的含義也一樣,就是從user表中取出一條數(shù)據(jù)。
不同的是
queryAll1queryAll2的返回值不一樣queryAll1沒有使用MapKey注解,返回值是User對象,符合SQL返回的只有一條記錄的語義queryAll2使用MapKey注解,但是返回值卻是一個Map對象?這似乎不符合SQL返回的語義。SQLselect * from user limit 1只返回一條記錄。怎么返回一個Map<String, User>對象呢?這就是MapKey這個注解的特別之處: 它能夠?qū)⒋娣艑ο蟮腖ist轉(zhuǎn)化為 key值為對象的某一屬性的Map。MapKey有一個屬性value,該屬性值填入的就是對象的屬性名,作為Map的key值??床欢@句話沒關(guān)系,看完執(zhí)行結(jié)果回頭再來看就懂了!
三、實戰(zhàn)
使用mybatis的SqlSession獲取Mapper代理對象,分別執(zhí)行
@org.junit.Test
public void testMapKey() throws IOException {
InputStream is = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User users1 = userMapper.queryAll1();
Map<String, User> users2 = userMapper.queryAll2();
System.out.println("不使用MapKey查詢: " + users3);
System.out.println("使用MapKey查詢: " + users4);
}
輸出結(jié)果
不使用MapKey查詢: User{id=1, username='111', password='222', birthday='333'}
使用MapKey查詢: {111=User{id=1, username='111', password='222', birthday='333'}}

可以看到,添加了MapKey注解的方法執(zhí)行結(jié)果Map的key就是注解里value值對應(yīng)的User對象的屬性值。value就是SQL查詢得到的結(jié)果User。
這就是MapKey這個注解的特別之處: 它能夠?qū)⒋娣艑ο蟮腖ist轉(zhuǎn)化為 key值為對象的某一屬性的Map。MapKey有一個屬性value,該屬性值填入的就是對象的屬性名,作為Map的key值
現(xiàn)在再來看這句話,是不是就能理解了?
實戰(zhàn)2——注意事項
Mapper接口中的方法標注了MapKey后,即使SQL返回多條結(jié)果,最終方法返回的結(jié)果只有一條。這是因為user表中的username字段全是一樣的。如果把MapKey注解中的value值改為其他user表中不一樣的字段,返回結(jié)果就會是多條記錄啦
@MapKey("username")
@Select("select * from user limit 10")
Map<String, User> queryAll5();
@MapKey("id")
@Select("select * from user limit 10")
Map<String, User> queryAll6();
執(zhí)行方法
Map<String, User> users5 = userMapper.queryAll5();
System.out.println("users5: " + users5);
Map<String, User> users6 = userMapper.queryAll6();
System.out.println("users6: " + users6);
輸出結(jié)果
users5: {111=User{id=11, username='111', password='222', birthday='333'}}
users6: {1=User{id=1, username='111', password='222', birthday='333'},
3=User{id=3, username='111', password='222', birthday='333'},
4=User{id=4, username='111', password='222', birthday='333'},
5=User{id=5, username='111', password='222', birthday='333'},
6=User{id=6, username='111', password='222', birthday='333'},
7=User{id=7, username='111', password='222', birthday='333'},
8=User{id=8, username='111', password='222', birthday='333'},
9=User{id=9, username='111', password='222', birthday='333'},
10=User{id=10, username='111', password='222', birthday='333'},
11=User{id=11, username='111', password='222', birthday='333'}}
如果標注了MapKey,則返回結(jié)果Map的value類型不可以是List,否則執(zhí)行方法會報錯。下面是錯誤示例。
@MapKey("username") // 執(zhí)行會報錯
@Select("select * from user limit 10")
Map<String, List<User>> queryAll5();
原理
@MapKey("username")
@Select("select * from user limit 10")
Map<String, User> queryAll5();
我們針對如上這個方法進行分析,在執(zhí)行SQL時會調(diào)用SqlSession中的如下代碼
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
//轉(zhuǎn)而去調(diào)用selectList
final List<?> list = selectList(statement, parameter, rowBounds);
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory());
final DefaultResultContext context = new DefaultResultContext();
for (Object o : list) {
//循環(huán)用DefaultMapResultHandler處理每條記錄
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
//注意這個DefaultMapResultHandler里面存了所有已處理的記錄(內(nèi)部實現(xiàn)可能就是一個Map),最后再返回一個Map
return mapResultHandler.getMappedResults();
}
來分析源碼
- 使用Executor查詢結(jié)果,這里的SQL是
select * from user limit 10,SQL執(zhí)行的結(jié)果返回給List對象,List中確實有10條記錄 - 構(gòu)造一個對象
DefaultMapResultHandler mapResultHandler,它是用來處理結(jié)果集的映射的, - 遍歷第一步中List得到的結(jié)果集對象
調(diào)用mapResultHandler.handleResult(context);方法將List結(jié)果集中每一條記錄對應(yīng)Mapkey中的屬性值取出,作為Map的key加入到集合里。handleResult源碼如下。其中主要關(guān)注這一行就可以了mappedResults.put(key, value);。
@Override
public void handleResult(ResultContext context) {
// TODO is that assignment always true?
//得到一條記錄
//這邊黃色警告沒法去掉了?因為返回Object型
final V value = (V) context.getResultObject();
//MetaObject.forObject,包裝一下記錄
//MetaObject是用反射來包裝各種類型
final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory);
final K key = (K) mo.getValue(mapKey);
mappedResults.put(key, value);
//這個類主要目的是把得到的List轉(zhuǎn)為Map
}
通過handleResult方法源碼可以看到,對于List結(jié)果集中的一條記錄,取出屬性username的值作為Map的key值添加到mappedResults集合中。那么如果key值相同就會被覆蓋!這就是實戰(zhàn)篇坑1的原理
最后是通過mapResultHandler.getMappedResults();方法返回第4步中的map最為最終方法的返回值。
總結(jié)
MapKey的作用:它能夠?qū)⒋娣艑ο蟮腖ist轉(zhuǎn)化為 key值為對象的某一屬性的Map。MapKey有一個屬性value,該屬性值填入的就是對象的屬性名,作為Map的key值
使用場景:可以針對結(jié)果集中的某個屬性去重,而不在乎其他字段是否重復(fù)
以上就是MyBatis的MapKey注解實例解析的詳細內(nèi)容,更多關(guān)于MyBatis MapKey注解的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深度解析SpringBoot中@Async引起的循環(huán)依賴
本文主要介紹了深度解析SpringBoot中@Async引起的循環(huán)依賴,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
idea將maven項目改成Spring boot項目的方法步驟
這篇文章主要介紹了idea將maven項目改成Spring boot項目的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09
Java設(shè)計模塊系列之書店管理系統(tǒng)單機版(一)
這篇文章主要為大家詳細介紹了Java單機版的書店管理系統(tǒng)設(shè)計模塊和思想第一章,感興趣的小伙伴們可以參考一下2016-08-08
深入了解HttpClient的ResponseHandler接口
這篇文章主要為大家介紹了深入了解HttpClient的ResponseHandler接口,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10

