美文网首页
mybatis框架下select带in批量查询,返回结果集无序问

mybatis框架下select带in批量查询,返回结果集无序问

作者: YukunWen | 来源:发表于2018-11-08 15:09 被阅读347次
    引言

    在实际项目中有的时候会多次连接数据库查询,有的时候会带上in的语句,在二次查询的时候返回的数据集就是没有顺序的。比如我们在微博平台上搜索某个用户,为了搜索速度够快,在els那边一般只存user的id和username。当根据username匹配到用户,获取到id后,还会在本地的数据库中根据id去查询用户更多的信息,比如sex,signature等字段。在第一次查询的时候是按照username匹配程度来排序的,而第二次查询的结果希望能与第一次查询的结果顺序保持一致。

    因此,如何保证in查询的结果与输入的一致是非常重要的。

    实操

    一. 辟谣

    有人说只要把resultType中的HashMap转换成LinkedHaspMap,就可以保持一致(网址:https://blog.csdn.net/qq_29115715/article/details/69958264
    经过博主亲自尝试,此种方法根本就无效!!

    二. 利用sql原声语句

    先来看一下普通的in语句出现的效果


    图1.普通的in语句

    利用order by field的效果


    图2.利用order by field的效果
    因此,利用mybatis的mapper.xml编写原生的xml也可以达到如此效果,代码如下:
    接口层
    @Repository
    @Mapper
    public interface UserMapper extends BaseDao<User> {
        List<User> yktestOrder(@Param("ids")List<Long> ids);
    }
    

    Mapper.xml

     <select id="yktestOrder" resultType="com.yuxun.fantuan.security.entity.User">
            select
            u.id,
            u.username
            FROM
            user u
            WHERE
            1 = 1
            <if test="null != ids and ids.size > 0">
            AND u.id IN
            <foreach item="item" index="index" collection="ids" open="(" separator="," close=")">
                #{item}
            </foreach>
                order by field(u.id,
                <foreach item="item" index="index" collection="ids" separator=",">
                    #{item}
                </foreach>
                )
            </if>
        </select>
    

    测试controller

        @Autowired
        private UserMapper userMapper;
        @PostMapping(value = "/yktest")
        @ResponseBody
        public RestResponse<String> yktest() throws Exception {
    
            List<Long> ids = new ArrayList<Long>(){{
                this.add(635L);
                this.add(1L);
                this.add(1179L);
            }};
            List<User> result1 = userMapper.selectBatchIds(ids);
            List<User> resutl2 = userMapper.yktestOrder(ids);
            System.out.println("result1");
            result1.forEach(System.out::println);
            System.out.println("result2");
            resutl2.forEach(System.out::println);
            return RestResponse.ok("执行完成");
        }
    

    最终的console输出的结果如下:

    图3.测试结果图
    注意:特别要小心xml中order by field的括号写法

    三. 自己编写utils方法进行转换

    思路
    • 传入的对象是一个list<User>或者是一个list<Map<String,Object>>类型。我们知道User继承Object,但是List<User>并不继承List<Object>。所以我们只能拿Object去接收待转顺序的结果集Object result,并且我们要知道该结果集的类型Class<?> resultClass
    • 同理,我们要知道待排序的list和它的类型分别为Object order,Class<?> orderClass。以及我们还要知道根据结果集result中哪个字段进行排序,String orderName
    • 接着我们转换顺序的思想就是,拿一个中间的tempMap,类型为Map<orderClass, resultClass>。先遍历result,拿到每个元素的orderName的值当做tempMap的key,每个元素自身当做value。然后我们在遍历order,把其中的每个元素从temp的map之中get出来,放到最终的结果List<resultClass> returnList之中。
    • 最后把 returnList返回。因为我们知道查询结果都是List<>类型的,所以才可以确定返回的就是要List<resultClass>。
      PS:有几个点需要注意一下:
      • map取key值的方法就是get("id"),而如果是User对象取值方式是getId()。所以二者需要做类型区别判断。
      • order之中可能存在一些id,在result之中并不存在,所以最后的从temp的map之中取元素的时候要做取到结果是否为空的判断。

    一句话概括就是用map作为中间变量,先把所有元素存进去,然后按照预想的顺序get出来add到返回的list中。

    最终代码如下:

    /**
         * 传入相应的查询结果和欲想顺序进行顺序重排
         *
         * @param result 传入的查询结果
         * @param resultClass 传入查询结果的类
         * @param order 传入的顺序List
         * @param orderClass 传入的顺序List的类型
         * @param orderName 传入的要排序的字段名
         * @param <resultClass>
         * @param <orderClass>
         * @return
         * @throws Exception
         */
        public static <resultClass, orderClass> List<resultClass> changeResultOrder(Object result, Class<?> resultClass,
                                                                                    Object order, Class<?> orderClass, String orderName) {
            List<resultClass> returnList = new ArrayList<>();
            Map<orderClass, resultClass> tempMap = new HashMap<>();
    
            for (resultClass aoo : (List<resultClass>) result) {
                if (resultClass.isAssignableFrom(Map.class)) {
                    Object b = MapUtils.getObject((Map) aoo, orderName);
                    orderClass cb = (orderClass) b;
                    if (orderClass.isAssignableFrom(Long.class)) {
                        cb = (orderClass) Long.valueOf(b + "");
                    }
                    tempMap.put(cb, aoo);
                } else {
                    try {
                        Method method = aoo.getClass().getMethod("get" + toUpperFirstChar(orderName));
                        orderClass b = (orderClass) method.invoke(aoo);
                        tempMap.put(b, aoo);
                    }catch (NoSuchMethodException | IllegalAccessException |InvocationTargetException e){
                        throw new FantuanRuntimeException("调用sqlHelpUtils出错,反射获取方法有误",e);
                    }
                }
            }
    
            for (orderClass boo : (List<orderClass>) order) {
                resultClass getResult = tempMap.get(boo);
                if (null != getResult){
                    returnList.add(getResult);
                }
            }
            return returnList;
        }
    

    总结

    个人比较推荐使用方法三,一是比较灵活,二是我们在用mybatis的时候很多时候会用mybatis的插件,不常在底层操作mapper.xml文件。正如博主在代码中用的

    <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus</artifactId>
          <version>2.1.9</version>
    </dependency>
    

    在查询的时候就直接用

     List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);
    

    此时用自己编写的代码去调整顺序就非常合适。

    方法三的代码和相应的测试用例已经放在github上面,有需要的读者可以自行下载:
    https://github.com/YukunWen/SqlHelperUtilsDemo

    相关文章

      网友评论

          本文标题:mybatis框架下select带in批量查询,返回结果集无序问

          本文链接:https://www.haomeiwen.com/subject/uemixqtx.html