剖析@MapKey,探究Mybatis将结果映射为Map的秘密


思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。

作者:毅航😜


在日常使用Mybatis进行开发时,不知道你是否遇到过这样的需求,即需要从一堆数据中找出自己所期待的某个数据。对于这样的需求,大致可以通过如下几种方式来进行处理:

  1. sql语句中拼接where条件,从而达到对数据筛选的目的,进而找出我们所想要的数据;
  2. 查询所有数据,然后通过循环遍历的方式找出自己所想要的那条数据信息
  3. 利用@MapKey注解,将查询到的数据存放在一个Map中,然后通过key进行获取。

其中,方式1和方式2的做法相对来说比较常规,在此便不重点分析了。接下来,我们重点来分析Mybaits内部@MapKey的使用和其背后的工作原理。

前言

MyBatis中,@MapKey 主要用于在映射查询结果到一个Map。换言之,当你执行一个查询并期望返回一个Map时,你可以使用@MapKey来进行结果集的映射。而Mybatis内部会将查询到的结果映射为一个key-value的形式。

接下来,先让我们将通过一个简单的例子来快速了解@MapKey的基本使用。

@MapKey使用示例

假设有一个User类,其有idaganame三个属性。此时,你想根据员工的id来获取整个User对象的映射。那么你的MyBatis映射器接口可以写为如下的形式:

java 复制代码
@Mapper
public interface EmployeeMapper {
    @Select("SELECT id, name,age FROM test_user")
    @MapKey("id")
    Map<Integer, User> getAllUser();
}

在上述例子中,当调用getAllUser方法后,将返回一个Map。其中每个员工的id是键,对应的User对象是值,具体执行结果如下所示。

不难发现,通过使用@MapKey你可以有效地将数据库查询的结果直接映射为Java中的Map结构。总之,当查询返回多个对象,并且你希望根据这些对象的某个属性来将它们组织成一个Map时,@MapKey非常有用。

熟悉了@MapKey的时候后,接下来我们再来看一看@MapKey背后的原理。

剖析@MapKey的背后的原理

在分析@MapKey之前,我们不妨先考虑这样一个问题,那就是如果我们要剖析Mybatis内部对于@MapKey的解析,我们应该从何处入手呢?

在专栏文章Mybatis流程分析(五): sql语句与接口中方法绑定的"细节"中,我们曾提到在Mybatis中,sql语句和方法的绑定是通过配置解析器将配置信息解析为MappedStatement来实现的。换言之,方法待执行的sql会存放在MappedStatement之中。 进一步,当我们调用Mapper中的方法时,其最终会执行Mapper接口中方法所绑定的sql

而在这一过程中会依赖一个MapperMethod的对象。正如Mybatis流程分析(六): Mybatis中方法和sql语句的桥梁------MapperProxy中提到的那样,在MyBatis中,MapperMethod 是一个内部类,其用于桥接MyBatisMapper接口和实际执行的SQL命令,这背后的大致逻辑如下:

  • 首先,当你调用一个Mapper接口中的方法时,MyBatis会使用MapperProxy代理这个调用。
  • 其次,MapperProxy在确定出被调用的方法后,会使用MapperMethod来执行相应的数据库操作。
  • 然后,MapperMethod确定方法的参数、SQL语句和返回类型,然后调用MyBatis的执行层来执行SQL语句。
  • 最终,执行完毕后,MapperMethod将结果处理成适当的返回类型并返回给调用者。

而在MapperMethod确定方法的参数信息时,其又需要借助一个名为MethodSignature的内部类。对于这个类,你只需知道其主要用于封装和处理与 Mapper 接口中定义的方法相关的元数据和操作。

(注:MethodSignature主要负责提取和管理与方法调用相关的信息,如参数、返回类型、是否存在注解等。换言之,你只需知道MethodSignature的主要就是为了记录方法的签名信息即可。)

话说到这个份上,最开始的问题是是不是已经很清晰了?如果我们要剖析@MapKey注解被解析的地方,是不是只要去MethodSignature类寻找就可以了?明白了这点,再来看看MethodSignature中的相关代码才不至于迷惑。

MethodSignature中有关@MapKey注解的代码

java 复制代码
public static class MethodSignature {

  // ...省略其他无关代码
  
  // 存放mapKey中配置的key信息
  private final String mapKey;
 
  public MethodSignature() {
    // ...省略其他无关代码
    this.mapKey = getMapKey(method);
  // ...省略其他无关代码
  }

事实上,在MethodSignature中有关@MapKey注解的解析全部委托给方法getMapKey来进行处理。而该方法的大致逻辑就是获取方法的上@MapKey然后将其中的key值信息,记录到mapKey字段中。

知晓了@MapKey的解析后,我们再来看Mybatis内部是如何将查询到结果封装为一个Map

Mybatis流程分析(九): 从JDBC出发讲透Mybatis结果集的处理逻辑中,我们曾提到过在Mybatis中,结果集的处理全部会委托给handleResult进行处理。而Mybatis内部默认使用的结果处理器为DefaultMapResultHandler。所以接下来,我们进入到DefaultMapResultHandler中一看Mybatis内部对于@MapKey的处理。这一过程调笔者就不带着注意分析了,感兴趣的可自己进行debug。整个过程的调用逻辑如下:

java 复制代码
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);
}

不难发现,在handleResult方法发现就是将我们制定的key取出为MapKey,然后将数据作为MapValue。其实,@MapKey背后的逻辑还是相对简单的,总结下来无非两步:

  • 注解解析。解析@MapKey中配置的属性字段,存放至变量mapKey中;
  • 结果集处理。循环处理结果集,依据配置好的key将结果集封装为一个map

总结

接下来,我们对Mybatis内部对于@MapKey的解析过程进行一个简单的总结。在Mybatis内部对于@MapKey的处理大致经历了如下过程:

  1. 注解解析 :当 MyBatis 解析你的Mapper接口时,它会识别方法上的 @MapKey 注解。这个注解包含一个值,指定了应该用作 Map 键的字段名。

  2. 执行 SQL 查询 :当调用标记有 @MapKey 注解的方法时,MyBatis 会执行相应的 SQL 查询。

  3. 结果集映射MyBatis 遍历SQL查询的结果集。对于结果集中的每一行:

    • 它首先创建一个新的对象,该对象的类型是 Mapper 接口方法的返回类型Map的值类型。
    • 然后,MyBatis 将当前行的数据填充到这个新创建的对象中。
  4. 键值对填充MyBatis 使用 @MapKey 注解指定的字段值作为键(Key),将新创建的对象作为值(Value),填充到最终的 Map 结构中。

总的来说@MapKey背后的逻辑还是相对简单的,不算很难,基本都是些Java基础知识的相关应用。希望文章对你有所帮助!

相关推荐
2401_854391087 分钟前
城镇住房保障:SpringBoot系统功能概览
java·spring boot·后端
hummhumm9 分钟前
Oracle 第29章:Oracle数据库未来展望
java·开发语言·数据库·python·sql·oracle·database
陈随易12 分钟前
兔小巢收费引发的论坛调研Node和Deno有感
前端·后端·程序员
聪明的墨菲特i17 分钟前
Django前后端分离基本流程
后端·python·django·web3
wainyz18 分钟前
Java NIO操作
java·开发语言·nio
工业3D_大熊23 分钟前
【虚拟仿真】CEETRON SDK在船舶流体与结构仿真中的应用解读
java·python·科技·信息可视化·c#·制造·虚拟现实
lzb_kkk32 分钟前
【JavaEE】JUC的常见类
java·开发语言·java-ee
爬山算法1 小时前
Maven(28)如何使用Maven进行依赖解析?
java·maven
hlsd#1 小时前
go mod 依赖管理
开发语言·后端·golang
陈大爷(有低保)1 小时前
三层架构和MVC以及它们的融合
后端·mvc