浅谈MyBatis中@MapKey的妙用

网友投稿 2096 2022-11-03

浅谈MyBatis中@MapKey的妙用

浅谈MyBatis中@MapKey的妙用

目录MyBatis @MapKey的妙用背景实现源码分析思考Mybatis @MapKey分析1. MapKey注解有啥功能2. MapKey的源码分析1. MapperMethod对MapKey的操作2. DefaultMapResultHandler是什么

MyBatis @MapKey的妙用

背景

在实际开发中,有一些sdWEkqeQs场景需要我们返回主键或者唯一键为Key、Entity为Value的Map集合,如Map,之后我们就可以直接通过map.get(key)的方式来获取Entity。

实现

MyBatis为我们提供了这种实现,Dao示例如下:

public interface UserDao {

@MapKey("id")

Map selectByIdList(@Param("idList") List idList);

}

需要注意的是:如果Mapper.xml中的select返回类型是List的元素,上面示例的话,resultType是User,因为selectMap查询首先是selectList,之后才是处理List。

源码分析

package org.apache.ibatis.session.defaults;

public class DefaultSqlSession implements SqlSession {

... ...

public Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {

final List> list = selectList(statement, parameter, rowBounds);

final DefaultMapResultHandler mapResultHandler = new DefaultMapResultHandler(mapKey,

configuration.getObjectFactory(), configuration.getObjectWrapperFactory());

final DefaultResultContext context = new DefaultResultContext();

for (Object o : list) {

context.nextResultObject(o);

mapResultHandler.handleResult(context);

}

Map selectedMap = mapResultHandler.getMappedResults();

return selectedMap;

}

... ...

}

selectMap方法其实是在selectList后的进一步处理,通过mapKey获取DefaultMapResultHandler类型的结果处理器,然后遍历list,调用handler的handleResult把每个结果处理后放到map中,最后返回map。

package org.apache.ibatis.executor.result;

public class DefaultMapResultHandler implements ResultHandler {

private final Map mappedResults;

... ...

public void handleResult(ResultContext context) {

// TODO is that assignment always true?

final V value = (V) context.getResultObject();

final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory);

// TODO is that assignment always true?

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

mappedResults.put(key, value);

}

... ...

}

可以看出DefaultMapResultHandler是通过mapKey从元数据中获取K,然后mappedResults.put(key, value)放到map中。

思考

@MapKey这种处理是在查询完后做的处理,实际上我们也可以自己写逻辑将List转成Map,一个Lambda表达式搞定,如下:

List list = userDao.selectByIdList(Arrays.asList(1,2,3));

Map map = list.stream().collect(Collectors.toMap(User::getId, user -> user));

Mybatis @MapKey分析

先上例子

@Test

public void testShouSelectStudentUsingMapperClass(){

//waring 就使用他作为第一个测试类

try (SqlSession session = sqlMapper.openSession()) {

StudentMapper mapper = session.getMapper(StudentMapper.class);

System.out.println(mapper.listStudentByIds(new int[]{1,2,3}));

}

}

@MapKey("id")

Map listStudentByIds(int[] ids);

select * from t_student

where id in

#{item}

结果

1. MapKey注解有啥功能

Mapkey可以让查询的结果组装成Map,Map的key是@MapKey指定的字段,Value是实体类。如上图所示

2. MapKey的源码分析

还是从源码分析一下他是怎么实现的,要注意MapKey不是写在Xml中的,而是标注在方法上的。所以,对于XML文件来说,他肯定不是在解析文件的时候操作的。对于Mapper注解实现来说,理论上来说是在解析的时候用的,但是对比XML的解析来说,应该不是。多说一点,想想Spring ,开始的时候都是XML,最后采用的注解,并且注解的功能和XML的对应起来,所以在解析XML是怎么解析的,在解析注解的时候就应该是怎么解析的。

还是老套路,点点看看,看看哪里引用到了他,从下图看到,用到的地方一个是MapperMethod,一个是MapperAnnotationBuilder,在MapperAnnotationBuilder里面只是判断了一下,没有啥实质性的操作,这里就不用管。只看前者。

1. MapperMethod对MapKey的操作

看过之前文章的肯定知道,MapperMethod是在哪里创建的。这个是在调用mapper接口的查询的时候创建的。接口方法的执行最终会调用到这个对象的execute方法

MapperMethod里面包含两个对象SqlCommand和MethodSignature,就是在MethodSignature里面引用了MapKey的

public MethodSignature(Configuration configuration, Class> mapperInterface, Method method) {

//判断此方法的返回值的类型

Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);

if (resolvedReturnType instanceof Class>) {

this.returnType = (Class>) resolvedReturnType;

} else if (resolvedReturnType instanceof ParameterizedType) {

this.returnType = (Class>) ((ParameterizedType) resolvedReturnType).getRawType();

} else {

this.returnType = method.getReturnType();

}

// 返回值是否为空

this.returnsVoid = void.class.equals(this.returnType);

// 返回是否是一个列表或者数组

this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();

// 返回值是否返回一个游标

this.returnsCursor = Cursor.class.equals(this.returnType);

// 返回值是否是一个Optional

this.returnsOptional = Optional.class.equals(this.returnType);

// 重点来了,这里会判断返回值是一个MapKey,并且会将MapKey里Value的值赋值给MapKey

this.mapKey = getMapKey(method);

// 返回值是否是一个map

this.returnsMap = this.mapKey != null;

this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);

this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); //找到方法参数里面 第一个 参数类型为ResultHandler的值

// 这里是处理方法参数里面的param注解, 注意方法参数里面有两个特殊的参数 RowBounds和 ResultHandler

// 这里会判断@param指定的参数,并且会将这些参数组成一个map,key是下标,value是param指定的参数,如果没有,就使用方法参数名

this.paramNameResolver = new ParamNameResolver(configuration, method);

}

上面的代码这次最重要的是mapKey的赋值操作getMapKey,来看看他是什么样子

private String getMapKey(Method method) {

String mapKey = null;

if (Map.class.isAssignableFrom(method.getReturnType())) {

final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);

if (mapKeyAnnotation != null) {

mapKey = mapKeyAnnotation.value();

}

}

return mapKey;

}

上面介绍了MapKey是在哪里解析的,下面分析Mapkey是怎么应用的,抛开所有的不说,围绕查询来说。经过上面的介绍。已经对查询的流程很清晰了,因为查询还是普通的查询,所以,MapKey在组装值的时候才会发送作用,下面就看看吧

还是老套路,既然赋值给MethodSignature的mapKey了,点点看看,哪里引用了他

下面的没有啥可看的,看看上面,在MapperMethod里面用到了,那就看看

//看这个名字就能知道,这是一个执行Map查询的操作

private Map executeForMap(SqlSession sqlSession, Object[] args) {

Map result;

Object param = method.convertArgsToSqlCommandParam(args);

if (method.hasRowBounds()) {

RowBounds rowBounds = method.extractRowBounds(args);

// 将Map传递给sqlSession了,那就一直往下走

result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);

} else {

result = sqlSession.selectMap(command.getName(), param, method.getMapKey());

}

return result;

}

一直点下去,就看到下面的这个了,可以看到,这里将mapKey传递给了DefaultMapResultHandler,对查询的结果进行处理。

@Override

public Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {

//这已经做了查询了

final List extends V> list = selectList(statement, parameter, rowBounds);

final DefaultMapResultHandler mapResultHandler = new DefaultMapResultHandler<>(mapKey,

configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());

final DefaultResultContext context = new DefaultResultContext<>();

// 遍历list,利用mapResultHandler处理List

for (V o : list) {

context.nextResultObject(o);

mapResultHandler.handleResult(context);

}

return mapResultHandler.getMappedResults();

}

这里很明确了,先做正常的查询,在对查询到的结果做处理(DefaultMapResultHandler)。

2. DefaultMapResultHandler是什么

/**

* @author Clinton Begin

*/

public class DefaultMapResultHandler implements ResultHandler {

private final Map mappedResults;

private final String mapKey;

private final ObjectFactory objectFactory;

private final ObjectWrapperFactory objectWrapperFactory;

private final ReflectorFactory reflectorFactory;

@SuppressWarnings("unchecked")

public DefaultMapResultHandler(String mapKey, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {

this.objectFactory = objectFactory;

this.objectWrapperFactory = objectWrapperFactory;

this.reflectorFactory = reflectorFactory;

this.mappedResults = objectFactory.create(Map.class);

this.mapKey = mapKey;

}

// 逻辑就是这里,

@Override

public void handleResult(ResultContext extends V> context) {

//拿到遍历的list的当前值。

final V value = context.getResultObject();

//构建MetaObject,

final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);

// TODO is that assignment always true?

// 获取mapKey指定的属性,放在mappedResults里面。

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

mappedResults.put(key, value);

}

// 返回结果

public Map getMappedResults() {

return mappedResults;

}

}

这里的逻辑很清晰,对查询查到的list。做遍历,利用反射获取MapKey指定的字段,并且组成Map,放在一个Map(mappedResults,这默认就是HashMap)里面。

问题?

1, 从结果中获取mapkey字段的操作,这个字段总是有的吗?

不一定,看这个例子,MapKey是一个不存在的属性值,那么在Map里面就会存在一个Null,这是Hashmap决定的。

综述:

在MapKey的使用中,要注意MapKey中Value字段的唯一性,否则就会造成Key值覆盖的操作。同时也要注意,Key要肯定存在,否则结果就是null,(如果有特殊操作的话,就另说)话说回来,这里我觉得应该增加强校验。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Sparrow是一款针对移动web app开发的前端轻量级框架.
下一篇:SpringBoot项目下的JUnit测试
相关文章

 发表评论

暂时没有评论,来抢沙发吧~