首頁 > 軟體

MyBatis的MapKey註解範例解析

2023-02-08 22:02:36

使用

mybatis中有很多實用的註解,但是平時想不起來使用。今天就來講一下MapKey是如何使用的

說明:本文基於mybatis原生框架3.3.0-SNAPSHOT

一、資料準備

資料庫準備一張user表,插入一點測試資料

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。這兩個方法執行的SQL是一樣的,SQL的含義也一樣,就是從user表中取出一條資料。

不同的是

  • queryAll1 queryAll2的返回值不一樣
  • queryAll1沒有使用MapKey註解,返回值是User物件,符合SQL返回的只有一條記錄的語意
  • queryAll2使用MapKey註解,但是返回值卻是一個Map物件?這似乎不符合SQL返回的語意。SQLselect * from user limit 1只返回一條記錄。怎麼返回一個Map<String, User>物件呢?這就是MapKey這個註解的特別之處: 它能夠將存放物件的List轉化為 key值為物件的某一屬性的Map。MapKey有一個屬性value,該屬性值填入的就是物件的屬性名,作為Map的key值。看不懂這句話沒關係,看完執行結果回頭再來看就懂了!

三、實戰

使用mybatis的SqlSession獲取Mapper代理物件,分別執行

@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);
}

輸出結果

不使用MapKey查詢: User{id=1, username='111', password='222', birthday='333'}
使用MapKey查詢: {111=User{id=1, username='111', password='222', birthday='333'}}

可以看到,新增了MapKey註解的方法執行結果Map的key就是註解裡value值對應的User物件的屬性值。value就是SQL查詢得到的結果User。

這就是MapKey這個註解的特別之處: 它能夠將存放物件的List轉化為 key值為物件的某一屬性的Map。MapKey有一個屬性value,該屬性值填入的就是物件的屬性名,作為Map的key值

現在再來看這句話,是不是就能理解了?

實戰2——注意事項

Mapper介面中的方法標註了MapKey後,即使SQL返回多條結果,最終方法返回的結果只有一條。這是因為user表中的username欄位全是一樣的。如果把MapKey註解中的value值改為其他user表中不一樣的欄位,返回結果就會是多條記錄啦

@MapKey("username")
@Select("select * from user limit 10")
Map<String, User> queryAll5();
@MapKey("id")
@Select("select * from user limit 10")
Map<String, User> queryAll6();

執行方法

Map<String, User> users5 = userMapper.queryAll5();
System.out.println("users5: " + users5);
Map<String, User> users6 = userMapper.queryAll6();
System.out.println("users6: " + users6);

輸出結果

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,則返回結果Map的value型別不可以是List,否則執行方法會報錯。下面是錯誤範例。

@MapKey("username") // 執行會報錯
@Select("select * from user limit 10")
Map<String, List<User>> queryAll5();

原理

@MapKey("username")
@Select("select * from user limit 10")
Map<String, User> queryAll5();

我們針對如上這個方法進行分析,在執行SQL時會呼叫SqlSession中的如下程式碼

public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
  //轉而去呼叫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) {
    //迴圈用DefaultMapResultHandler處理每條記錄
    context.nextResultObject(o);
    mapResultHandler.handleResult(context);
  }
  //注意這個DefaultMapResultHandler裡面存了所有已處理的記錄(內部實現可能就是一個Map),最後再返回一個Map
  return mapResultHandler.getMappedResults();
}

來分析原始碼

  • 使用Executor查詢結果,這裡的SQL是select * from user limit 10,SQL執行的結果返回給List物件,List中確實有10條記錄
  • 構造一個物件DefaultMapResultHandler mapResultHandler,它是用來處理結果集的對映的,
  • 遍歷第一步中List得到的結果集物件

呼叫mapResultHandler.handleResult(context);方法將List結果集中每一條記錄對應Mapkey中的屬性值取出,作為Map的key加入到集合裡。handleResult原始碼如下。其中主要關注這一行就可以了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轉為Map
}

通過handleResult方法原始碼可以看到,對於List結果集中的一條記錄,取出屬性username的值作為Map的key值新增到mappedResults集合中。那麼如果key值相同就會被覆蓋!這就是實戰篇坑1的原理

最後是通過mapResultHandler.getMappedResults();方法返回第4步中的map最為最終方法的返回值。

總結

MapKey的作用:它能夠將存放物件的List轉化為 key值為物件的某一屬性的Map。MapKey有一個屬性value,該屬性值填入的就是物件的屬性名,作為Map的key值

使用場景:可以針對結果集中的某個屬性去重,而不在乎其他欄位是否重複

以上就是MyBatis的MapKey註解範例解析的詳細內容,更多關於MyBatis MapKey註解的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com