最近在开发的一个业务功能需要从一批数据中根据业务字段提取数据,对于这个需求可能有的同学就直接用for或者stream循环的方式进行处理了。但是,作为一个资深的搬砖人,秉承着能够框架完成的绝不手写的勤奋思维,我们可以用Mybatis调用数据库查询出数据后直接用@MapKey注解直接封装成以业务字段为key的Map,后续直接根据key进行数据获取。
MyBatis就不用多说了,就是一个基于Java语言的持久层框架,它通过XML描述符或注解将对象与存储过程或SQL语句进行映射,并提供了普通SQL查询、存储过程和高级映射等操作方式,使得操作数据库变得非常方便。
查看@MapKey注解源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MapKey {
String value();
}
由以上源码可知,MapKey注解应用于运行时的方法上,也就是我们的DAO的方法上。
用MapKey注解可以直接返回一个以指定字段为key,整体数据为value的Map,通过这个Map我们可以直接获取指定字段的具体数据。
1、xml增加查询sql语句
2、DAO增加调用方法
/**
* 流转商机根据用户分组
* @param belongCompanyChildId
* @author senfel
* @date 2023/10/27 9:50
* @return
*/
@MapKey("belongUserId")
Map businessGroupByUser(@Param("belongCompanyChildId") Long belongCompanyChildId);
3、增加测试用例
/**
* MapKeyAnnotationTest
* @author senfel
* @version 1.0
* @date 2023/10/27 9:57
*/
@Slf4j
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class MapKeyAnnotationTest {
@Autowired
private CustomerBusinessDao customerBusinessDao;
/**
* MapKey resultMap
* @author senfel
* @date 2023/10/27 9:59
* @return void
*/
@Test
public void resultMap(){
Map longIntegerMap = customerBusinessDao.businessGroupByUser(2l);
System.err.println(longIntegerMap);
}
}
4、debug模式运行测试用例
由上图所知,我们调用Mybatis执行sql语句后直接返回的就是已经封装为Map的数据,并且Map的key为我们注解中的@MapKey(“belongUserId”),数据为查询出的字段。
得到这个数据结构后,我们直接可以在业务代码获取指定 belongUserId 的数据进行处理,不用再进行循环什么的操作。
当然可能有的同学会问为什么不一次查询一条呢,这个就要根据业务逻辑来了,比如我们这里的根据子公司统计商机数据量并根据所属人分组,还有一些查询数据量大单次查询影响性能的情况,都是需要考虑一次性从数据库拉取多条数据。
MapKey源码逻辑比较简单,大致就是如果我们在方法上增加了@MapKey注解,Mybatis框架就会帮助我们根据指定key封装为指定类型的Map数据。
进入MapperMethod查看具体执行逻辑,有一个方法签名MethodSignature方法获取了注解中的key字段:
//方法签名,在签名中获取了我们注解中指定的key字段
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);
//获取指定key字段
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
//直接获取到了我们dao层@MapKey注解写入的value
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;
}
既然获取了注解key字段,我们我们就可以继续查找看什么地方调用了这个 this.mapKey
如上图所示,我们看到了在Mybatis执行sql语句的时候会将mapKey中key字段传入,我们继续进入查看执行逻辑:
//执行返回map
private Map executeForMap(SqlSession sqlSession, Object[] args) {
Map result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
直接进入默认的方法查看执行逻辑,这里用DefaultMapResultHandler默认的map结果处理器,并循环调用handleResult方法进行数据封装:
//查看封装为map的方法
@Override
public Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
final List extends V> list = selectList(statement, parameter, rowBounds);
//源码这里用DefaultMapResultHandler默认的map结果处理器
final DefaultMapResultHandler mapResultHandler = new DefaultMapResultHandler(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
final DefaultResultContext context = new DefaultResultContext();
//循环数据库返回的对象
for (V o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
return mapResultHandler.getMappedResults();
}
我们直接进入DefaultMapResultHandler默认的map结果处理器,查看handleResult方法发现就是将我们制定的key取出为Map的Key,然后将数据作为Map的Value:
@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) {
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);
}
Mybatis框架是Java后端常用的持久层框架,其中的@MapKey注解可以直接返回指定类型的Map。其核心原理就是对数据集合循环处理将指定字段作为key,数据作为value直接返回一个Map让我们直接使用以完成特定的业务功能。
⭐️路漫漫其修远兮,吾将上下而求索