Mybatis @MapKey注解返回指定Map源码解析与用例

23 篇文章 3 订阅
3 篇文章 1 订阅

前言

最近在开发的一个业务功能需要从一批数据中根据业务字段提取数据,对于这个需求可能有的同学就直接用for或者stream循环的方式进行处理了。但是,作为一个资深的搬砖人,秉承着能够框架完成的绝不手写的勤奋思维,我们可以用Mybatis调用数据库查询出数据后直接用@MapKey注解直接封装成以业务字段为key的Map,后续直接根据key进行数据获取。

技术积累

什么是MyBatis

MyBatis就不用多说了,就是一个基于Java语言的持久层框架,它通过XML描述符或注解将对象与存储过程或SQL语句进行映射,并提供了普通SQL查询、存储过程和高级映射等操作方式,使得操作数据库变得非常方便。

@MapKey注解

查看@MapKey注解源码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MapKey {
  String value();
}

由以上源码可知,MapKey注解应用于运行时的方法上,也就是我们的DAO的方法上。
用MapKey注解可以直接返回一个以指定字段为key,整体数据为value的Map,通过这个Map我们可以直接获取指定字段的具体数据。

用例展示

1、xml增加查询sql语句

<resultMap id="businessGroupByUserMap" type="com.ysjr.base.domain.entity.vo.roam.RoamBusinessVo">
    <result column="count" property="count" jdbcType="INTEGER"/>
    <result column="belongUserId" property="belongUserId" jdbcType="BIGINT"/>
</resultMap>
<!--流转根据用户查询商机分组-->
<select id="businessGroupByUser" parameterType="map" resultMap="businessGroupByUserMap">
    select count(customer_business_id) as count,belong_user_id as belongUserId from t_customer_business
    where delete_status = 1
      and belong_company_child_id = #{belongCompanyChildId}
      and clue_business_type = 2
      and `status` in ('BS002','BS003','BS004','BS005')
    group by belong_user_id;
</select>

2、DAO增加调用方法

/**
 * 流转商机根据用户分组
 * @param belongCompanyChildId
 * @author senfel
 * @date 2023/10/27 9:50
 * @return
 */
@MapKey("belongUserId")
Map<Long, RoamBusinessVo> 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<Long, RoamBusinessVo>  longIntegerMap = customerBusinessDao.businessGroupByUser(2l);
        System.err.println(longIntegerMap);

    }
}

4、debug模式运行测试用例

在这里插入图片描述

由上图所知,我们调用Mybatis执行sql语句后直接返回的就是已经封装为Map的数据,并且Map的key为我们注解中的@MapKey(“belongUserId”),数据为查询出的字段。

得到这个数据结构后,我们直接可以在业务代码获取指定 belongUserId 的数据进行处理,不用再进行循环什么的操作。

当然可能有的同学会问为什么不一次查询一条呢,这个就要根据业务逻辑来了,比如我们这里的根据子公司统计商机数据量并根据所属人分组,还有一些查询数据量大单次查询影响性能的情况,都是需要考虑一次性从数据库拉取多条数据。

MapKey注解源码解析

MapKey源码逻辑比较简单,大致就是如果我们在方法上增加了@MapKey注解,Mybatis框架就会帮助我们根据指定key封装为指定类型的Map数据。

首先我们进入@MapKey源码查看其引用的类:
在这里插入图片描述

进入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 <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;
}

继续往下查看Mybatis如何对结果封装为Map的:
在这里插入图片描述

直接进入默认的方法查看执行逻辑,这里用DefaultMapResultHandler默认的map结果处理器,并循环调用handleResult方法进行数据封装:

//查看封装为map的方法
@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);
  //源码这里用DefaultMapResultHandler默认的map结果处理器
  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();
}

我们直接进入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让我们直接使用以完成特定的业务功能。

⭐️路漫漫其修远兮,吾将上下而求索 🔍

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
MyBatis 中的 @MapKey 注解和 resultMap 中的 <map> 标签都用于将查询结果转换为 Map 类型,但它们的使用方式略有不同。 @MapKey 注解用于将查询结果集中的某一列作为 Map 的键,将整个结果集放入 Map返回。它通常与 selectMap() 方法一起使用,示例如下: ```java @MapKey("id") Map<Integer, User> selectUserMap(); ``` 上述代码中,@MapKey("id") 注解指定将查询结果集中的 id 列作为 Map 的键,将整个结果集转换为 Map<Integer, User> 类型返回。 而 <map> 标签则用于将查询结果集中的多列转换为一个 Map 类型的属性,通常用于一对多关系的映射。示例如下: ```xml <resultMap id="orderMap" type="Order"> <id property="id" column="id"/> <result property="orderNo" column="order_no"/> <result property="createTime" column="create_time"/> <collection property="orderItems" ofType="OrderItem"> <id property="id" column="item_id"/> <result property="name" column="item_name"/> <result property="quantity" column="item_quantity"/> </collection> <map property="extra" columnPrefix="extra_"> <key column="name"/> <value column="value"/> </map> </resultMap> ``` 上述代码中,<map> 标签用于将查询结果集中以 extra_ 前缀开头的多列转换为一个 Map<String, Object> 类型的属性,其中 name 列作为 Map 的键,value 列作为 Map 的值。 需要注意的是,@MapKey 注解和 <map> 标签都需要指定一个属性作为 Map 的键,如果没有指定,则默认将整个查询结果集转换为 Map 类型返回
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小沈同学呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值