思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
在日常使用Mybatis
进行开发时,不知道你是否遇到过这样的需求,即需要从一堆数据中找出自己所期待的某个数据。对于这样的需求,大致可以通过如下几种方式来进行处理:
- 在
sql
语句中拼接where
条件,从而达到对数据筛选的目的,进而找出我们所想要的数据; - 查询所有数据,然后通过
循环
遍历的方式找出自己所想要的那条数据信息 - 利用
@MapKey
注解,将查询到的数据存放在一个Map
中,然后通过key
进行获取。
其中,方式1
和方式2
的做法相对来说比较常规,在此便不重点分析了。接下来,我们重点来分析Mybaits
内部@MapKey
的使用和其背后的工作原理。
前言
在MyBatis
中,@MapKey
主要用于在映射查询结果到一个Map
。换言之,当你执行一个查询并期望返回一个Map
时,你可以使用@MapKey
来进行结果集的映射。而Mybatis
内部会将查询到的结果映射为一个key-value
的形式。
接下来,先让我们将通过一个简单的例子来快速了解@MapKey
的基本使用。
@MapKey
使用示例
假设有一个User
类,其有id
、aga
和name
三个属性。此时,你想根据员工的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
是一个内部类,其用于桥接MyBatis
的Mapper
接口和实际执行的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
取出为Map
的Key
,然后将数据作为Map
的Value
。其实,@MapKey
背后的逻辑还是相对简单的,总结下来无非两步:
- 注解解析。解析
@MapKey
中配置的属性字段,存放至变量mapKey
中; - 结果集处理。循环处理结果集,依据配置好的
key
将结果集封装为一个map
。
总结
接下来,我们对Mybatis
内部对于@MapKey
的解析过程进行一个简单的总结。在Mybatis
内部对于@MapKey
的处理大致经历了如下过程:
-
注解解析 :当
MyBatis
解析你的Mapper
接口时,它会识别方法上的@MapKey
注解。这个注解包含一个值,指定了应该用作Map
键的字段名。 -
执行 SQL 查询 :当调用标记有
@MapKey
注解的方法时,MyBatis
会执行相应的SQL
查询。 -
结果集映射 :
MyBatis
遍历SQL
查询的结果集。对于结果集中的每一行:- 它首先创建一个新的对象,该对象的类型是
Mapper
接口方法的返回类型Map
的值类型。 - 然后,
MyBatis
将当前行的数据填充到这个新创建的对象中。
- 它首先创建一个新的对象,该对象的类型是
-
键值对填充 :
MyBatis
使用@MapKey
注解指定的字段值作为键(Key)
,将新创建的对象作为值(Value)
,填充到最终的Map
结构中。
总的来说@MapKey
背后的逻辑还是相对简单的,不算很难,基本都是些Java
基础知识的相关应用。希望文章对你有所帮助!