开发者

MyBatis的MapKey注解实例解析

目录
  • 使用
  • 一、数据准备
  • 二、Mapper配置
    • UserMapper接口
  • 三、实战
    • 实战2——注意事项
      • 原理
        • 总结

          使用

          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 NU编程LL,
            PRIMARY KEY (`id`)
          ) ENGINE=InnoDB AUTO_INCREMENT=1018 DEFAULT CHARSET=utf8mb4
          
          select * from user;
          

          MyBatis的MapKey注解实例解析

          二、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,该属性值填入的就是对象www.devze.com的属性名,作为Map的key值。看不懂这句话没关系,看完执行结果回头再来看就懂了!

          三、实战

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

          @org.junit.Test
          public vphpoid 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'}}

          MyBatis的MapKey注解实例解析

          可以看到,添加了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&g开发者_JAVA开发t; 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&php#39;, 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 * frwww.devze.comom 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注解的资料请关注我们其它相关文章!

          0

          上一篇:

          下一篇:

          精彩评论

          暂无评论...
          验证码 换一张
          取 消

          最新开发

          开发排行榜