MyBatis的MapKey注解实例解析

 更新时间:2023年02月08日 11:04:02   作者:念念清晰  
这篇文章主要为大家介绍了MyBatis的MapKey注解实例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

使用

mybatis中有很多实用的注解,但是平时想不起来使用。今天就来讲一下MapKey是如何使用的

说明:本文基于mybatis原生框架3.3.0-SNAPSHOT

一、数据准备

数据库准备一张user表,插入一点测试数据

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `password` varchar(20) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `birthday` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1018 DEFAULT CHARSET=utf8mb4
select * from user;

二、Mapper配置

UserMapper接口

public interface UserMapper {
    @Select("select * from user limit 1")
    List<User> queryAll1();
    @MapKey("username")
    @Select("select * from user limit 1")
    Map<String, User> queryAll2();
}

这里我们的mapper接口只有两个方法queryAll1 queryAll2。这两个方法执行的SQL是一样的,SQL的含义也一样,就是从user表中取出一条数据。

不同的是

  • queryAll1 queryAll2的返回值不一样
  • queryAll1没有使用MapKey注解,返回值是User对象,符合SQL返回的只有一条记录的语义
  • queryAll2使用MapKey注解,但是返回值却是一个Map对象?这似乎不符合SQL返回的语义。SQLselect * from user limit 1只返回一条记录。怎么返回一个Map<String, User>对象呢?这就是MapKey这个注解的特别之处: 它能够将存放对象的List转化为 key值为对象的某一属性的Map。MapKey有一个属性value,该属性值填入的就是对象的属性名,作为Map的key值。看不懂这句话没关系,看完执行结果回头再来看就懂了!

三、实战

使用mybatis的SqlSession获取Mapper代理对象,分别执行

@org.junit.Test
public void testMapKey() throws IOException {
    InputStream is = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    SqlSession sqlSession = factory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User users1 = userMapper.queryAll1();
    Map<String, User> users2 = userMapper.queryAll2();
    System.out.println("不使用MapKey查询: " + users3);
    System.out.println("使用MapKey查询: " + users4);
}

输出结果

不使用MapKey查询: User{id=1, username='111', password='222', birthday='333'}
使用MapKey查询: {111=User{id=1, username='111', password='222', birthday='333'}}

可以看到,添加了MapKey注解的方法执行结果Map的key就是注解里value值对应的User对象的属性值。value就是SQL查询得到的结果User。

这就是MapKey这个注解的特别之处: 它能够将存放对象的List转化为 key值为对象的某一属性的Map。MapKey有一个属性value,该属性值填入的就是对象的属性名,作为Map的key值

现在再来看这句话,是不是就能理解了?

实战2——注意事项

Mapper接口中的方法标注了MapKey后,即使SQL返回多条结果,最终方法返回的结果只有一条。这是因为user表中的username字段全是一样的。如果把MapKey注解中的value值改为其他user表中不一样的字段,返回结果就会是多条记录啦

@MapKey("username")
@Select("select * from user limit 10")
Map<String, User> queryAll5();
@MapKey("id")
@Select("select * from user limit 10")
Map<String, User> queryAll6();

执行方法

Map<String, User> users5 = userMapper.queryAll5();
System.out.println("users5: " + users5);
Map<String, User> users6 = userMapper.queryAll6();
System.out.println("users6: " + users6);

输出结果

users5: {111=User{id=11, username='111', password='222', birthday='333'}}
users6: {1=User{id=1, username='111', password='222', birthday='333'}, 
        3=User{id=3, username='111', password='222', birthday='333'}, 
        4=User{id=4, username='111', password='222', birthday='333'}, 
        5=User{id=5, username='111', password='222', birthday='333'}, 
        6=User{id=6, username='111', password='222', birthday='333'}, 
        7=User{id=7, username='111', password='222', birthday='333'}, 
        8=User{id=8, username='111', password='222', birthday='333'}, 
        9=User{id=9, username='111', password='222', birthday='333'}, 
        10=User{id=10, username='111', password='222', birthday='333'}, 
        11=User{id=11, username='111', password='222', birthday='333'}}

如果标注了MapKey,则返回结果Map的value类型不可以是List,否则执行方法会报错。下面是错误示例。

@MapKey("username") // 执行会报错
@Select("select * from user limit 10")
Map<String, List<User>> queryAll5();

原理

@MapKey("username")
@Select("select * from user limit 10")
Map<String, User> queryAll5();

我们针对如上这个方法进行分析,在执行SQL时会调用SqlSession中的如下代码

public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
  //转而去调用selectList
  final List<?> list = selectList(statement, parameter, rowBounds);
  final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
      configuration.getObjectFactory(), configuration.getObjectWrapperFactory());
  final DefaultResultContext context = new DefaultResultContext();
  for (Object o : list) {
    //循环用DefaultMapResultHandler处理每条记录
    context.nextResultObject(o);
    mapResultHandler.handleResult(context);
  }
  //注意这个DefaultMapResultHandler里面存了所有已处理的记录(内部实现可能就是一个Map),最后再返回一个Map
  return mapResultHandler.getMappedResults();
}

来分析源码

  • 使用Executor查询结果,这里的SQL是select * from user limit 10,SQL执行的结果返回给List对象,List中确实有10条记录
  • 构造一个对象DefaultMapResultHandler mapResultHandler,它是用来处理结果集的映射的,
  • 遍历第一步中List得到的结果集对象

调用mapResultHandler.handleResult(context);方法将List结果集中每一条记录对应Mapkey中的属性值取出,作为Map的key加入到集合里。handleResult源码如下。其中主要关注这一行就可以了mappedResults.put(key, value);

@Override
public void handleResult(ResultContext context) {
  // TODO is that assignment always true?
  //得到一条记录
  //这边黄色警告没法去掉了?因为返回Object型
  final V value = (V) context.getResultObject();
  //MetaObject.forObject,包装一下记录
  //MetaObject是用反射来包装各种类型
  final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory);
  final K key = (K) mo.getValue(mapKey);
  mappedResults.put(key, value);
  //这个类主要目的是把得到的List转为Map
}

通过handleResult方法源码可以看到,对于List结果集中的一条记录,取出属性username的值作为Map的key值添加到mappedResults集合中。那么如果key值相同就会被覆盖!这就是实战篇坑1的原理

最后是通过mapResultHandler.getMappedResults();方法返回第4步中的map最为最终方法的返回值。

总结

MapKey的作用:它能够将存放对象的List转化为 key值为对象的某一属性的Map。MapKey有一个属性value,该属性值填入的就是对象的属性名,作为Map的key值

使用场景:可以针对结果集中的某个属性去重,而不在乎其他字段是否重复

以上就是MyBatis的MapKey注解实例解析的详细内容,更多关于MyBatis MapKey注解的资料请关注脚本之家其它相关文章!

相关文章

  • 详解如何在项目中应用SpringSecurity权限控制

    详解如何在项目中应用SpringSecurity权限控制

    本文主要介绍了如何在项目中应用SpringSecurity权限控制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • Java中StringBuilder与StringBuffer的区别

    Java中StringBuilder与StringBuffer的区别

    在Java编程中,字符串的拼接是一项常见的操作。为了有效地处理字符串的拼接需求,Java提供了两个主要的类:StringBuilder和StringBuffer,本文主要介绍了Java中StringBuilder与StringBuffer的区别,感兴趣的可以了解一下
    2023-08-08
  • JavaGUI事件监听机制超详细讲解

    JavaGUI事件监听机制超详细讲解

    Java事件监听器是由事件类和监听接口组成,自定义一个事件前,必须提供一个事件的监听接口以及一个事件类。JAVA中监听接口是继承java.util.EventListener的类,事件类继承java.util.EventObject的类
    2023-03-03
  • java将excel转为pdf的方法步骤

    java将excel转为pdf的方法步骤

    之前工作需要,查了挺多种Excel转PDF的方法,下面这篇文章主要给大家介绍了关于java将excel转为pdf的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-01-01
  • Hibernate Validator实现更简洁的参数校验及一个util

    Hibernate Validator实现更简洁的参数校验及一个util

    这篇文章主要介绍了Hibernate Validator实现更简洁的参数校验及一个util,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-05-05
  • 详解Spring Boot Junit单元测试

    详解Spring Boot Junit单元测试

    本篇文章主要介绍了详解Spring Boot Junit单元测试,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • mybatis创建一个或多个新用户 insert 字段和表名不确定时动态添加问题

    mybatis创建一个或多个新用户 insert 字段和表名不确定时动态添加问题

    这篇文章主要介绍了mybatis创建一个或多个新用户 insert 字段和表名不确定时动态添加问题,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-02-02
  • Java权重随机的实现方法

    Java权重随机的实现方法

    这篇文章主要介绍了Java权重随机的实现方法,实例分析了权重随机算法的原理与完整实现方法,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-01-01
  • Spring mvc文件上传下载代码实例

    Spring mvc文件上传下载代码实例

    这篇文章主要介绍了Spring mvc文件上传下载代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • 解决eclipse上传svn忽略target文件夹的坑

    解决eclipse上传svn忽略target文件夹的坑

    这篇文章主要介绍了解决eclipse上传svn忽略target文件夹的坑,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01

最新评论