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

文章目录

    • 前言
    • 技术积累
      • 什么是MyBatis
      • @MapKey注解
    • 用例展示
    • MapKey注解源码解析
    • 写在最后

前言

最近在开发的一个业务功能需要从一批数据中根据业务字段提取数据,对于这个需求可能有的同学就直接用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让我们直接使用以完成特定的业务功能。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/110571.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

场景交易额超40亿,海尔智家三翼鸟开始收获

文 | 螳螂观察 作者 | 余一 随着双十一的到来&#xff0c;国内的消费情绪再次被点燃。在这类大促之下&#xff0c;品牌们就像一个个天体&#xff0c;不断引动着市场潮汐&#xff0c;期待自己能触发更大的“海潮效应”。 所谓“海潮效应”是指&#xff0c;海水因天体的引力而…

Linux的基础常用指令

常用指令汇及其功能 ls 列出当前文件夹有哪些文件 ls -a显示所有文件&#xff0c;包含隐藏的文件和文件夹pwd显示当前是在哪个文件夹下mkdirmkdir名字→创建文件夹cdcd名字→进入某个指定文件夹cd .. 退回上层文件夹(cd后有空格) Tab键自动补全&#xff1a;文件或文件名太长&a…

【原创】java+swing+mysql志愿者管理系统设计与实现

摘要&#xff1a; 志愿者管理系统是一个用于管理志愿者以及活动报名的系统&#xff0c;提高志愿者管理的效率&#xff0c;同时为志愿者提供更好的服务和体验。本文主要介绍如何使用javaswingmysql去实现一个志愿者管理系统。 功能分析&#xff1a; 系统主要提供给管理员和志…

CSS中的栅格布局

CSS中的栅格布局 在写前端项目的时候&#xff0c;我之前一直习惯使用flex布局&#xff0c;flex布局写起来比较随心&#xff0c;几乎可以实现任意形式的页面布局。不过自从B占看到某位大佬的grid布局后&#xff0c;发现布局居然还可以这么玩&#xff0c;正好自己在写一个vue3的…

github搜索技巧探索

毕设涉及到推荐系统&#xff0c;那么就用搜索推荐系统相关资料来探索一下GitHub的搜搜技巧 文章目录 1. 基础搜索2. 限定在特定仓库搜索3. 按照语言搜索4. 按照star数量搜索5. 搜索特定用户/组织的仓库6. 查找特定文件或路径7. 按时间搜索8. 搜索不包含某个词的仓库9. 搜索特定…

pytorch 入门 (五)案例三:乳腺癌识别-VGG16实现

本文为&#x1f517;小白入门Pytorch内部限免文章 &#x1f368; 本文为&#x1f517;小白入门Pytorch中的学习记录博客&#x1f366; 参考文章&#xff1a;【小白入门Pytorch】乳腺癌识别&#x1f356; 原作者&#xff1a;K同学啊 在本案例中&#xff0c;我将带大家探索一下深…

用大白话聊聊SpringBoot的自动配置原理(面试题详解)

首先&#xff0c;SpringBoot的自动配置不等于自动装配&#xff01; 自动配置是Auto-Configuration&#xff0c;针对的是SpringBoot中的配置类&#xff0c; 而自动装配是Autowire&#xff0c;针对的是Spring中的依赖注入。 进入主题&#xff1a; 自动配置简单来说就是自动去把…

Cesium 问题:在 cesium 中加载 geojson 文件,绘制带边框的多边形并设置贴地后,边框不展示,该怎么配置呢?

文章目录 问题分析问题 在 cesium 中加载 geojson 文件,绘制带边框的多边形并设置贴地后,边框不展示,该怎么配置呢? 代码如下: var promise = Cesium.GeoJsonDataSource.load(data/obstacle/ExOceanData/result.geojson

自动化测试的一些问题合集

问题1、 ipykernel_launcher.py: error: unrecognized arguments: usage: ipykernel_launcher.py [-h] [--id ID] [--test TEST] [--env ENV] ipykernel_launcher.py: error: unrecognized arguments: --ip127.0.0.1 --stdin9003 --control9001 --hb9000 --Session.signature_…

SAML- 安全断言标记语言

一、概念 安全断言标记语言&#xff08;SAML&#xff09;是一种开放标准&#xff0c;用于在各方之间&#xff08;特别是身份提供商和服务提供商之间&#xff09;交换身份验证和授权数据。SAML 是一种基于XML的安全断言标记语言&#xff08;服务提供商用来做出访问控制决策的语句…

python使用ffmpeg来制作音频格式转换工具(优化版)

简介:一个使用python加上ffmpeg模块来进行音频格式转换的工具。 日志: 20231030:第一版,设置了简单的UI布局和配色,实现音频转为Mp3、AAC、wav、flac四种格式。可解析音频并显示信息,可设置转换后的保存路径 UI界面: 编程平台:visual studio code 编程语言:python 3…

基于 Center 的 3D 目标检测和跟踪

论文地址&#xff1a;https://arxiv.org/abs/2006.11275 论文代码&#xff1a;https://github.com/tianweiy/CenterPoint 3D 目标通常表示为点云中的 3D Boxes。 CenterPoint 在第一阶段&#xff0c;使用关键点检测器检测对象的中心&#xff0c;然后回归到其他属性&#xff0…

AcWing 第127场周赛 构造矩阵

构造题目&#xff0c;考虑去除掉最后一行最后一列先进行考虑&#xff0c;假设除了最后一行和最后一列都已经排好了&#xff08;你可以随便排&#xff09;&#xff0c;那么分析知最后一个数字由限制以外其他都已经确定了&#xff0c;无解的情况是k为-1 并且n&#xff0c;m的奇偶…

C++进阶语法——STL 标准模板库(上)(Standard Template Library)【学习笔记(六)】

文章目录 STL 标准模板库1、 STL简介2、STL容器的类别3、STL迭代器的类别4、STL算法的类别5、泛型编程&#xff08;generic programming&#xff09;6、C模板&#xff08;template&#xff09;6.1 函数模板&#xff08;function template&#xff09;6.2 类模板&#xff08;cla…

MAMP Pro 6.8.1

MAMP Pro是一款MacPHP/MySQL开发环境软件&#xff0c;可以将电脑变成一个完整的Web开发环境。无论个人开发者、网站管理员还是团队协作&#xff0c;MAMP Pro都提供了强大的工具和便捷的管理方式&#xff0c;能够更加高效地构建和测试网站。 MAMP Pro的基本功能包括集成AMP环境&…

【网络协议】聊聊套接字socket

网络编程我们知道是通过socket进行编程的&#xff0c;其实socket也是基于TCP和UDP协议进行编程的。但是在socket层面是感知不到下层的&#xff0c;所以在设置参数的时候&#xff0c;其实是端到端协议智商的网络层和传输层。TCP是数据流所以设置为SOCK_STREAM&#xff0c;而UDP是…

实现基于 Azure DevOps 的数据库 CI/CD 最佳实践

数据库变更一直是整个应用发布过程中效率最低、流程最复杂、风险最高的环节&#xff0c;也是 DevOps 流程中最难以攻克的阵地。那我们是否能在具体的 CI/CD 流程中&#xff0c;像处理代码那样处理数据库变更呢&#xff1f; DORA 调研报告 DORA&#xff08;DevOps Research &am…

​学习一下,什么是预包装食品?​

预包装食品&#xff0c;指预先定量包装或者制作在包装材料和容器中的食品&#xff1b;包括预先定量包装以及预先定量制作在包装材质和容器中并且在一定量限范围内具有统一的质量或体积标识的食品。简单说&#xff0c; 就是指在包装完成后即具有确定的量值&#xff0c;这一确定的…

Ajax学习笔记第4天

做决定之前仔细考虑&#xff0c;一旦作了决定就要勇往直前、坚持到底&#xff01; 【1 模仿百度招聘】 整个流程展示&#xff1a; 1.文件目录 2.页面效果展示及代码 data中的page1数据展示 2.1 主页 index.html:index里面代码部分解释 underscore.js :模板页面的相关代码 &…

2023最新版本 FreeRTOS教程 -1-标准库移植FreeRTOS

源码下载 官网下载驱动 点击直达 源码剪裁 剪裁之后的图片,找我免费获取 添加进MDK 配置滴答定时器 全部工程获取 查看下方头像