MyBatis的一个分页问题

2019-10-04  本文已影响0人  我要做大牛23333

目前独立负责一个单独的产品服务,技术栈是用的SpringBoot2 + MyBatis,分页用的MyBatis-PageHelper插件,版本是5.1.8

在项目中有一个非常复杂的查询语句,是用作模糊查询的,拢共LEFT JOIN了5张表,因为参数很多,又涉及到aggregation聚合操作,所以在MyBatis的xml里写了很多分支和逻辑操作。分页还是采用原始的PageHelper.start(from, to)这种方式。

先说下这个分页原理,其实就是MyBatis在做真正的Query之前有一个interceptor,会先把要执行的查询语句做一个包装变成:

SELECT COUNT(0) FROM 
(要执行的查询语句) table_count

先取到总数,然后再根据limit和offset做分页。

开始以为很简单,但奇怪的是分页居然出了问题,具体的表现就是,比如说一页取40个对象,有时候就会出现一页只有20多个,但总数和分页的个数是对的。稳定复现的问题可能很好定位,但时有时无的问题就很难解决。首先看下有没有人遇到过类似的问题,所以先试查阅MyBatis-Pagehelper官方文档,发现有类似的分页问题issue提到需要升级版本,从5.1.8再升级到5.1.10发现并没有任何变化。
这时候没发现有人遇到这种相似问题后的做法一般都是梳理下思路,从数据库的层面先排查下问题,把MyBatis执行的SQL打印出来,然后在数据库里执行,但发现有个有意思的现象就是比如从数据库里查询到有40条结果,这是分页后的结果,但页面上确只有38条,仔细比对后发现最后几条数据的id是有重复的,这是因为做了多次join,并且在resultmap中存在collection这种nested的嵌套型数据结构,不考虑分页的话,MyBatis自己会按照定义好的resultMap和结果集进行组合返回我们期望的数据结构。

所以结论结论就是MyBatis-Pagehelper这个插件如果用数据库的分页去实现分页的话,对于collection这种一对多或多对多的关系映射其实是没有办法实现分页的,最简单的分页办法就是全部取出来,然后放到List中取subList,最后再自己封装一个PageInfo对象即可,我也是这么做的。

封装的代码我先贴到这里: 其中users这个List就是一个subList,fromIndex是起始index,toIndex是截取到的结束index,total是所有user的总数,pageNumber是用户看到第几页,pageSize是一页有多少User

public class PageInfoBuilder {

    public static PageInfo<User> build(List<User> users, int fromIndex, int toIndex, int total, int pageNumber, int pageSize) {
        if (fromIndex < 0 || fromIndex >= users.size() || toIndex <= 0 || toIndex < fromIndex) {
            return PageInfo.of(Collections.emptyList());
        }

        PageInfo<User> pageInfo = PageInfo.of(users.subList(fromIndex, toIndex));
        pageInfo.setPageNum(pageNumber);
        pageInfo.setPageSize(pageSize);
        pageInfo.setTotal(total);
        int totalPages = (int) Math.ceil((double) total / pageSize);
        int [] navigatepageNums = new int[totalPages];
        for (int i = 0; i < totalPages; i++) {
            navigatepageNums[i] = i + 1;
        }
        pageInfo.setStartRow(fromIndex);
        pageInfo.setEndRow(toIndex);
        pageInfo.setNavigatePages(totalPages);
        pageInfo.setNavigatepageNums(navigatepageNums);
        pageInfo.setPages(totalPages);

        calcNavigatepageNums(pageInfo, navigatepageNums);
        calcPage(pageInfo, pageNumber, totalPages);
        judgePageBoudary(pageInfo, pageNumber, totalPages);

        return pageInfo;
    }

    private static void calcNavigatepageNums(PageInfo<Template> templatePageInfo, int[] navigatepageNums) {
        if (navigatepageNums.length > 0) {
            templatePageInfo.setNavigateFirstPage(navigatepageNums[0]);
            templatePageInfo.setNavigateLastPage(navigatepageNums[navigatepageNums.length - 1]);
        }
    }

    private static void calcPage(PageInfo<Template> templatePageInfo, int pageNumber, int totalPages) {
        if (pageNumber > 1) {
            templatePageInfo.setPrePage(pageNumber - 1);
        }

        if (pageNumber < totalPages) {
            templatePageInfo.setNextPage(pageNumber + 1);
        }
    }

    private static void judgePageBoudary(PageInfo<Template> templatePageInfo, int pageNumber, int totalPages) {
        templatePageInfo.setIsFirstPage(pageNumber == 1);
        templatePageInfo.setIsLastPage(pageNumber == totalPages || totalPages == 0);
        templatePageInfo.setHasPreviousPage(pageNumber > 1);
        templatePageInfo.setHasNextPage(pageNumber < totalPages);
    }

}

这样处理完后分页就正常了,考虑到目前的搜索数据集并不是很大,所以可以考虑这种全部load到内存然后取subList的做法,但如果很大的话,比如几百万的用户的话这种做法可能就不是很合适,需要另寻解决方案。

最后在处理完问题后,在官方的wiki里有这么句话,嵌套的数据映射是不支持分页的,捂脸。
https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/Important.md

上一篇 下一篇

猜你喜欢

热点阅读