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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!
2017-12-20 微信公众号 文章的爬虫系统
2016-12-20 REST Security with JWT using Java and Spring Security
2015-12-20 阿里感悟(九)- 能力和晋升(转)
2015-12-20 阿里感悟(十四)-如何带新员工(转)
2015-12-20 Java NIO系列教程(三) Buffer(转)