MyBatis的MapKey注解實(shí)例解析
使用
mybatis中有很多實(shí)用的注解,但是平時(shí)想不起來使用。今天就來講一下MapKey是如何使用的
說明:本文基于mybatis原生框架3.3.0-SNAPSHOT
一、數(shù)據(jù)準(zhǔn)備
數(shù)據(jù)庫準(zhǔn)備一張user
表,插入一點(diǎn)測(cè)試數(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接口只有兩個(gè)方法queryAll1
queryAll2
。這兩個(gè)方法執(zhí)行的SQL是一樣的,SQL的含義也一樣,就是從user表中取出一條數(shù)據(jù)。
不同的是
queryAll1
queryAll2
的返回值不一樣queryAll1
沒有使用MapKey注解,返回值是User對(duì)象,符合SQL返回的只有一條記錄的語義queryAll2
使用MapKey注解,但是返回值卻是一個(gè)Map對(duì)象?這似乎不符合SQL返回的語義。SQLselect * from user limit 1
只返回一條記錄。怎么返回一個(gè)Map<String, User>
對(duì)象呢?這就是MapKey這個(gè)注解的特別之處: 它能夠?qū)⒋娣艑?duì)象的List轉(zhuǎn)化為 key值為對(duì)象的某一屬性的Map。MapKey有一個(gè)屬性value,該屬性值填入的就是對(duì)象的屬性名,作為Map的key值。看不懂這句話沒關(guān)系,看完執(zhí)行結(jié)果回頭再來看就懂了!
三、實(shí)戰(zhàn)
使用mybatis的SqlSession獲取Mapper代理對(duì)象,分別執(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值對(duì)應(yīng)的User對(duì)象的屬性值。value就是SQL查詢得到的結(jié)果User。
這就是MapKey這個(gè)注解的特別之處: 它能夠?qū)⒋娣艑?duì)象的List轉(zhuǎn)化為 key值為對(duì)象的某一屬性的Map。MapKey有一個(gè)屬性value,該屬性值填入的就是對(duì)象的屬性名,作為Map的key值
現(xiàn)在再來看這句話,是不是就能理解了?
實(shí)戰(zhàn)2——注意事項(xiàng)
Mapper接口中的方法標(biāo)注了MapKey后,即使SQL返回多條結(jié)果,最終方法返回的結(jié)果只有一條。這是因?yàn)閡ser表中的username字段全是一樣的。如果把MapKey注解中的value值改為其他user表中不一樣的字段,返回結(jié)果就會(huì)是多條記錄啦
@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'}}
如果標(biāo)注了MapKey,則返回結(jié)果Map的value類型不可以是List,否則執(zhí)行方法會(huì)報(bào)錯(cuò)。下面是錯(cuò)誤示例。
@MapKey("username") // 執(zhí)行會(huì)報(bào)錯(cuò) @Select("select * from user limit 10") Map<String, List<User>> queryAll5();
原理
@MapKey("username") @Select("select * from user limit 10") Map<String, User> queryAll5();
我們針對(duì)如上這個(gè)方法進(jìn)行分析,在執(zhí)行SQL時(shí)會(huì)調(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); } //注意這個(gè)DefaultMapResultHandler里面存了所有已處理的記錄(內(nèi)部實(shí)現(xiàn)可能就是一個(gè)Map),最后再返回一個(gè)Map return mapResultHandler.getMappedResults(); }
來分析源碼
- 使用Executor查詢結(jié)果,這里的SQL是
select * from user limit 10
,SQL執(zhí)行的結(jié)果返回給List對(duì)象,List中確實(shí)有10條記錄 - 構(gòu)造一個(gè)對(duì)象
DefaultMapResultHandler mapResultHandler
,它是用來處理結(jié)果集的映射的, - 遍歷第一步中List得到的結(jié)果集對(duì)象
調(diào)用mapResultHandler.handleResult(context);
方法將List結(jié)果集中每一條記錄對(duì)應(yīng)Mapkey中的屬性值取出,作為Map的key加入到集合里。handleResult源碼如下。其中主要關(guān)注這一行就可以了mappedResults.put(key, value);
。
@Override public void handleResult(ResultContext context) { // TODO is that assignment always true? //得到一條記錄 //這邊黃色警告沒法去掉了?因?yàn)榉祷豋bject型 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); //這個(gè)類主要目的是把得到的List轉(zhuǎn)為Map }
通過handleResult方法源碼可以看到,對(duì)于List結(jié)果集中的一條記錄,取出屬性u(píng)sername的值作為Map的key值添加到mappedResults集合中。那么如果key值相同就會(huì)被覆蓋!這就是實(shí)戰(zhàn)篇坑1的原理
最后是通過mapResultHandler.getMappedResults();
方法返回第4步中的map最為最終方法的返回值。
總結(jié)
MapKey的作用:它能夠?qū)⒋娣艑?duì)象的List轉(zhuǎn)化為 key值為對(duì)象的某一屬性的Map。MapKey有一個(gè)屬性value,該屬性值填入的就是對(duì)象的屬性名,作為Map的key值
使用場(chǎng)景:可以針對(duì)結(jié)果集中的某個(gè)屬性去重,而不在乎其他字段是否重復(fù)
以上就是MyBatis的MapKey注解實(shí)例解析的詳細(xì)內(nèi)容,更多關(guān)于MyBatis MapKey注解的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深度解析SpringBoot中@Async引起的循環(huán)依賴
本文主要介紹了深度解析SpringBoot中@Async引起的循環(huán)依賴,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02JavaFX實(shí)現(xiàn)拖拽結(jié)點(diǎn)效果
這篇文章主要為大家詳細(xì)介紹了JavaFX實(shí)現(xiàn)拖拽結(jié)點(diǎn)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12idea將maven項(xiàng)目改成Spring boot項(xiàng)目的方法步驟
這篇文章主要介紹了idea將maven項(xiàng)目改成Spring boot項(xiàng)目的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Java判斷一個(gè)實(shí)體是不是空的簡(jiǎn)單方法
這篇文章主要給大家介紹了關(guān)于Java判斷一個(gè)實(shí)體是不是空的簡(jiǎn)單方法,實(shí)際項(xiàng)目中我們會(huì)有很多地方需要判空校驗(yàn),文中給出了詳細(xì)的示例代碼,需要的朋友可以參考下2023-07-07Java設(shè)計(jì)模塊系列之書店管理系統(tǒng)單機(jī)版(一)
這篇文章主要為大家詳細(xì)介紹了Java單機(jī)版的書店管理系統(tǒng)設(shè)計(jì)模塊和思想第一章,感興趣的小伙伴們可以參考一下2016-08-08Springboot集成Kafka進(jìn)行批量消費(fèi)及踩坑點(diǎn)
本文主要介紹了Springboot集成Kafka進(jìn)行批量消費(fèi)及踩坑點(diǎn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12深入了解HttpClient的ResponseHandler接口
這篇文章主要為大家介紹了深入了解HttpClient的ResponseHandler接口,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10