原来PageHelper还有这个坑!
创始人
2025-07-05 01:31:28
0

哈喽,大家好,我是了不起。

Mybatis插件PageHelper很多人都会使用吧,有一次Copy的时候忘了去掉PageHelper.startPaeg()方法,结果导致数据查不到,debug 的时候发现第一次查询没有数据,再查询一次就有数据了,这个坑竟然也花了一点时间,那么今天就把这个坑填平。

这里我们一起看看其原理以及日常使用注意事项。

如何使用PageHelper

PageHelper是Mybatis-Plus中的一个插件,主要用于实现数据库的分页查询功能。其核心原理是将传入的页码和条数赋值给一个Page对象,并保存到本地线程ThreadLocal中。接下来,PageHelper会进入Mybatis的拦截器环节,在拦截器中获取并处理刚才保存在ThreadLocal中的分页参数。这些分页参数会与原本的SQL语句和内部已经定义好的SQL进行拼接,从而完成带有分页处理的SQL语句的构建。

Pagehelper 有很多种使用方式,下面列出官方给出的几种使用方式。

//第一种,RowBounds方式的调用
    List list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));

    //第二种,Mapper接口方式的调用,推荐这种使用方式。
    PageHelper.startPage(1, 10);
    List list = countryMapper.selectIf(1);

    //第三种,Mapper接口方式的调用,推荐这种使用方式。
    PageHelper.offsetPage(1, 10);
    List list = countryMapper.selectIf(1);

    //第四种,参数方法调用
    //存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
    public interface CountryMapper {
        List selectByPageNumSize(
                @Param("user") User user,
                @Param("pageNum") int pageNum,
                @Param("pageSize") int pageSize);
    }
    //配置supportMethodsArguments=true
    //在代码中直接调用:
    List list = countryMapper.selectByPageNumSize(user, 1, 10);

    //第五种,参数对象
    //如果 pageNum 和 pageSize 存在于 User 对象中,只要参数有值,也会被分页
    //有如下 User 对象
    public class User {
        //其他fields
        //下面两个参数名和 params 配置的名字一致
        private Integer pageNum;
        private Integer pageSize;
    }
    //存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
    public interface CountryMapper {
        List selectByPageNumSize(User user);
    }
    //当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页
    List list = countryMapper.selectByPageNumSize(user);

    //第六种,ISelect 接口方式
    //jdk6,7用法,创建接口
    Page page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
        @Override
        public void doSelect() {
            countryMapper.selectGroupBy();
        }
    });
    //jdk8 lambda用法
    Page page = PageHelper.startPage(1, 10).doSelectPage(()-> countryMapper.selectGroupBy());

    //也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
    pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
        @Override
        public void doSelect() {
            countryMapper.selectGroupBy();
        }
    });
    //对应的lambda用法
    pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> countryMapper.selectGroupBy());

    //count查询,返回一个查询语句的count数
    long total = PageHelper.count(new ISelect() {
        @Override
        public void doSelect() {
            countryMapper.selectLike(country);
        }
    });
    //lambda
    total = PageHelper.count(()->countryMapper.selectLike(country));

PageHelper原理

其核心原理是将传入的页码和条数赋值给一个Page对象,并保存到本地线程ThreadLocal中。

下面以常见的使用方式看一下:

PageHelper.startPage(1, 10, orderBy);

经过一些列的循环俄罗斯套娃调用之后,来到了这里:

public static  Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
        Page page = new Page(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
        //当已经执行过orderBy的时候
        Page oldPage = getLocalPage();
        if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }
        setLocalPage(page);
        return page;
    }
  1. 重点在setLocalPage(page) 这个方法,将page对象放到了静态变量ThreadLocal中。

图片图片

  1. 查询接口进来的时候, PageHelper中的Threadlocal对象中就保存的该线程对应的分页参数,在调用查询的时候就会拿出来使用。

PageHelper 中有写了一个 com.github.pagehelper.PageInterceptor , 这里是执行分页的地方。如果你想要写其他的拦截器,也可以自定义一个mybatis plugin包拦截器,在这里对sql进行处理。

图片图片

  1. 在PageInterceptor  中有一个主要interceptor方法,在方法中需要判断是否需要分页,如果需要分页,则获取分页信息,查询数据量等。

图片图片

  1. 接下来,PageHelper会进入Mybatis的拦截器环节,在拦截器中获取并处理刚才保存在ThreadLocal中的分页参数。这些分页参数会与原本的SQL语句和内部已经定义好的SQL进行拼接,从而完成带有分页处理的SQL语句的构建。需要注意一点的是在finally中remove掉 ThreadLocal对象中当前线程的page对象。

图片图片

  1. 调用skip方法, 并获取分页参数 判断是否需要分页。

图片图片

  1. 从静态ThreadLocal中获取page对象,

图片图片

PageHelper在执行这一过程时,会判断SQL的类型,只有当该SQL是查询操作时,才会进入分页逻辑。并且,在进入分页逻辑处理后,PageHelper会通过反射获取该方法的参数,判断是否存在IPage对象(这是Mybatis-Plus中的另一个分页对象)的实现类。如果存在这样的实现类,那么也会进行相应的分页处理。

PageHelper注意事项

使用PageHelper分页推荐使用  PageHelper.startPage(1, 10)  这种方式。

另外startPage语句最好紧挨着查询语句,避免中间抛出异常,没有办法清除ThreadLocal中当前线程的page对象。

下面看一下官方给出的不安全的用法 和推荐的例子:

不安全用法:下面的方法如果不走查询的话,page对象就会保留在当前线程中,算是一个内存泄漏,如果下一个mybatis查询方法刚好是这个线程的时候,就会被动分页。

PageHelper.startPage(1, 10);
List list;
if(param1 != null){
    list = countryMapper.selectIf(param1);
} else {
    list = new ArrayList();
}

推荐用法,主要是紧挨着mybatis查询方法,查询结束后,page对象就被清理,对线程后续的运行不受影响。

List list;
if(param1 != null){
    PageHelper.startPage(1, 10);
    list = countryMapper.selectIf(param1);
} else {
    list = new ArrayList();
}

回到最开始的那个坑,原因就在于错误的使用了PageHelper.startPage(page,size), 这里的分页参数只是调用下游系统的分页参数,不应该再这里使用,所以在第一页的时候是没问题的,到第二页的时候,查询就出问题了,数据库中没有那么多数据,所以会查不到数据。 debug的时候,执行一次数据查询操作,把page对象消耗掉了,第二次查询的数据没有分页参数,所以就可以正常查询出数据。

相关内容

热门资讯

PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
粉嫩如何诠释霸道 东芝M805... “霸道粉”是个什么玩意东芝M805拿过来的时候,笔者扑哧笑了,不是笑这款笔记本,而是笑这款产品的颜色...
什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
全面诠释网络负载均衡 负载均衡的出现大大缓解了服务器的压力,更是有效的利用了资源,提高了效率。那么我们现在来说一下网络负载...
如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
30分钟搞定iOS自定义相机 最近公司的项目中用到了相机,由于不用系统的相机,UI给的相机切图,必须自定义才可以。就花时间简单研究...
Intel将Moblin社区控... 本周二,非营利机构Linux基金会宣布,他们将担负起Moblin社区的管理工作,而这之前,Mobli...