MyBatis中@MapKey使用详解

Posted 叶长风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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方法就分析到这了。

以上是关于MyBatis中@MapKey使用详解的主要内容,如果未能解决你的问题,请参考以下文章

@MapKey----mybatis返回Map

@MapKey----mybatis返回Map

Mybatis中注解@MapKey的使用

MyBatis源码分析之@SelectProvider注解使用详解

MyBatis源码分析之@ResultMap注解详解

mybatis使用group by分组