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方法就分析到這了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章