PageHelper的使用及避坑

一、PageHelper介绍及使用

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

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

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

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

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

    //第四种,参数方法调用
    //存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
    public interface CountryMapper {
        List<Country> selectByPageNumSize(
                @Param("user") User user,
                @Param("pageNum") int pageNum,
                @Param("pageSize") int pageSize);
    }
    //配置supportMethodsArguments=true
    //在代码中直接调用:
    List<Country> 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<Country> selectByPageNumSize(User user);
    }
    //当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页
    List<Country> list = countryMapper.selectByPageNumSize(user);

    //第六种,ISelect 接口方式
    //jdk6,7用法,创建接口
    Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
        @Override
        public void doSelect() {
            countryMapper.selectGroupBy();
        }
    });
    //jdk8 lambda用法
    Page<Country> 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 <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
    Page<E> page = new Page<E>(pageNum, pageSize, count);
    page.setReasonable(reasonable);
    page.setPageSizeZero(pageSizeZero);
    //当已经执行过orderBy的时候
    Page<E> oldPage = getLocalPage();
    if (oldPage != null && oldPage.isOrderByOnly()) {
        page.setOrderBy(oldPage.getOrderBy());
    }
    setLocalPage(page);
    return page;
}

        重点在setLocalPage(page)这个方法,将page对象放到了静态变量ThreadLocal中。

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

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

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

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

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

        从静态ThreadLocal中获取Page对象。

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

三、PageHelper注意事项

1、确保PageHelper.startPage()被正确使用:

        必须在Mapper的查询方法之前调用PageHelper.startPage(),并且确保该查询方法会消费掉分页设置,否则分页设置可能会影响后续的查询。如果传参对象的分页字段是pageNum和pageSize,即使没有显式调用PageHelper.startPage(),PageHeler也可能自动应用分页,这可能导致预期之外的行为。

2、Stream流导致分页失效的问题:

        PageHelper中调用数据库查询返回的集合信息,实际上返回的是Page类型的集合(继承ArrayList),然后业务实现层中查询数据库之后有使用Stream流对集合进行处理,处理之后返回的数据类型为ArrayList,这也就是导致分页失效的原因。

        避免这样的问题出现,可以使用for循环替代Stream遍历处理。或者在用Stream流处理后,手动设置分页参数。

3、避免性能问题:

        MyBatis-PageHelper是通过动态修改SQL来实现分页的,这在大量或高并发场景下可能导致性能瓶颈。因此,需要注意优化,特别是在处理大数据量时。

4、注意查询语句复杂度:

        对于复杂的查询语句,Mybatis-PageHelper可能会出现问题。在使用PageHelper时,应确保查询语句的复杂性不会导致分页功能失效或产生错误的结果。

5、测试分页逻辑:

        在实际应用中,彻底测试分页逻辑时非常重要的,以确保分页功能按照预期工作。这包括测试边界条件,如第一页、最后一页以及中间页码的查询。

### PageHelper 分页插件使用教程 #### 导入依赖 为了在项目中使用PageHelper分页插件,首先需要导入相应的依赖。对于Maven项目来说,可以在`pom.xml`文件中加入如下配置来引入PageHelper: ```xml <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.8</version> </dependency> ``` 此操作会自动下载并集成所需的库到工程环境中[^4]。 #### 插件初始化设置 接着,在Spring Boot应用程序或其他基于Spring框架的应用程序里,可以通过Java配置类或者XML形式注册该插件。通常推荐采用更简洁的Java Config方式来进行全局配置: ```java @Configuration public class MyBatisConfig { @Bean public PageHelper pageHelper() { PageHelper pageHelper = new PageHelper(); Properties p = new Properties(); p.setProperty("offsetAsPageNum", "true"); p.setProperty("rowBoundsWithCount", "true"); p.setProperty("reasonable", "true"); pageHelper.setProperties(p); return pageHelper; } } ``` 上述代码片段展示了如何创建一个自定义配置实例,并设置了几个常用的属性选项以优化性能表现和用户体验[^2]。 #### 实现基本分页逻辑 当完成了前期准备工作之后,便可以着手编写具体的业务处理函数了。下面给出了一段典型的DAO层接口实现示例,用于执行带有限制条件的数据检索请求: ```java // 假设有一个名为UserMapper.java 的 Mapper 接口 @Select("SELECT * FROM users WHERE status=#{status}") List<User> selectUsersByStatus(@Param("status") Integer status); // 对应的服务端控制器方法内调用 mapper 方法前加上分页语句即可轻松达成目的 int pageNum = 1; // 当前页面编号,默认第一页 int pageSize = 10; // 每页显示记录数 PageHelper.startPage(pageNum, pageSize); List<User> userList = userMapper.selectUsersByStatus(1); // 正常查询列表数据 System.out.println(new PageInfo<>(userList).getTotal()); // 输出总条目数量 ``` 这里的关键在于调用了静态工具类 `PageHelper.startPage()` 来指定当前要展示哪一部分的内容范围;随后正常发起SQL指令读取目标表中的信息集合;最后借助于辅助对象 `PageInfo<T>` 可方便地获取有关本次查询的一些统计指标,比如总数、是否有下一页等附加元数据[^1]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值