MyBatis中@MapKey使用詳解


MyBatis中@MapKey使用詳解
我們在上一篇文章中講到在Select返回類型中是返回Map時,是對方法中是否存在注解@MapKey,這個注解我也是第一次看到,當時我也以為是純粹的返回單個數據對象的Map類型,但是發現還是有些不同的,這個可以用來返回多條記錄,具體用法與分析如下。

@MapKey用法
我查了一下MapKey的用法,這里加上MapKey注解后,還有指定一個字段作為返回Map中的key,這里一般也就是使用唯一鍵來做key,我這就使用id做key吧。

在UserMapper中添加一個根據address查詢的方法,方便返回多條數據,UserMapper在Mybatis源碼解析之配置加載(一)中有,這里就不再完全展示了,添加的方法如下:

@MapKey("id")
@ResultMap("BaseResultMap")
@Select("select * from user where hotel_address = #{address};")
Map<Long, User> getUserByAddress(@Param("address") String address);


我定義的返回類型為Map<Long, user>,這里id做key,user對象為value,但是要注意的就是User對象中有hotelAddress字段,如果就只加@MapKey注解多半難以映射user對象中的hotelAddress字段,這里加上ResultMap注解試試,不行再想別的辦法。

測試用例如下:

Map<Long, User> userMap = userMapper.getUserByAddress("beijing");
for (Map.Entry<Long, User> entry : userMap.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}


執行程序,倒是如之前想的一樣,結果如下圖:


hotelAddress字段值正常顯示出來了,可以把@ResultMap注解去掉試試,結果如下圖:

hotelAddress字段顯示為null。

這里就不再過多的演示各種用法,這里返回User對象可行,返回Map同樣可行,下面開始就開始具體分析@MapKey的使用源碼。

2. 源碼分析
此處還是要回到Select查詢處,如下:

case SELECT:

if (method.returnsVoid() && method.hasResultHandler()) {
  executeWithResultHandler(sqlSession, args);
  result = null;
} else if (method.returnsMany()) {
  result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
  result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
   result = executeForCursor(sqlSession, args);
} else {
   Object param = method.convertArgsToSqlCommandParam(args);
   result = sqlSession.selectOne(command.getName(), param);
}


進入到第三種情況executeForMap方法中。

private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
}
return result;
}

 

繼續轉入selectMap方法中,如上次所知,這個方法最終調用的仍然是selectList方法,但是我們要搞清楚@MapKey發生作用的位置與原理,在這里要提一句的是,這里向下傳輸的method.getMapKey()就是我們@MapKey注解中填的value,也就是id。

@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
final List<? extends V> list = selectList(statement, parameter, rowBounds);
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
final DefaultResultContext<V> context = new DefaultResultContext<V>();
for (V o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
return mapResultHandler.getMappedResults();
}


我們在調試代碼時可知list這里已經是user對象了。

顯而易見的是對查詢結果的處理已經在selectList(statement, parameter, rowBounds)方法中了,這里原本想把@ResultMap也一起拿出來說一下,然后發現@ResultMap應該從頭開始講起,所以這個就留到下次再說吧。

從上面代碼塊中中知MapKey生效處應該是nextResultObject與handleResult方法中,我們先看nextResultObject做的事情。

public void nextResultObject(T resultObject) {
   resultCount++;
   this.resultObject = resultObject;
}

 

做了一個類似於初始化的工作,那么重點就是在於handleResult方法中了,轉到handleResult方法中。

@Override
public void handleResult(ResultContext<? extends V> context) {
final V value = context.getResultObject();
final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
// TODO is that assignment always true?
final K key = (K) mo.getValue(mapKey);
mappedResults.put(key, value);
}


這里的value對象類型為User對象,MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory)這句應該是將user對象轉成MetaObject對象,然后通過mapKey取出對應屬性的值。

final K key = (K) mo.getValue(mapKey)

可以進getValue看看,到底是如何渠道id字段對應的值。

public Object getValue(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
return null;
} else {
return metaValue.getValue(prop.getChildren());
}
} else {
return objectWrapper.get(prop);
}
}

@Override
public Object get(PropertyTokenizer prop) {
if (prop.getIndex() != null) {
Object collection = resolveCollection(prop, object);
return getCollectionValue(prop, collection);
} else {
return getBeanProperty(prop, object);
}
}

private Object getBeanProperty(PropertyTokenizer prop, Object object) {
try {
Invoker method = metaClass.getGetInvoker(prop.getName());
try {
return method.invoke(object, NO_ARGUMENTS);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} catch (RuntimeException e) {
throw e;
} catch (Throwable t) {
throw new ReflectionException("Could not get property '" + prop.getName() + "' from " + object.getClass() + ". Cause: " + t.toString(), t);
}
}

 

這里通過獲取到id對應的方法getId,然后反射拿到id對應的值,這里的判斷還真多。

拿到id值以后就比較好辦了,直接將key和value保存進map中。

final K key = (K) mo.getValue(mapKey);
mappedResults.put(key, value);

 

然后在selectMap方法中進行返回MapResultSet操作。

@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
....
for (V o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
return mapResultHandler.getMappedResults();
}

 


從而我們得到Map形式的返回結果。

@MapKey作用位置以及Select中executeMap方法就分析到這了。
————————————————
版權聲明:本文為CSDN博主「葉長風」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u012734441/article/details/85861337

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM