mybatis+Pagehelper实现分页

冉丰茂
2023-12-01

环境

java:1.8
Intellij IDEA : 2020.3

前言

在springboot+mybatis的项目中,利用Pagehelper 实现分页。
CSDN,标题不能超过30个字符,真TMD的脑残设计,用屁股想出来的吧
因为项目里,mybatis没有好的分页功能,所以结合主流,整合了下Pagehelper。

引包

在父pom.xml的管理中添加:

<properties>
    <pagehelper-spring-boot.version>1.2.12</pagehelper-spring-boot.version>
</properties>
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>${pagehelper-spring-boot.version}</version>
</dependency>

接着在相应的模块中引入:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

配置

application.properties文件中添加:

#分页插件
pagehelper.helper-dialect=mysql
# 分页合理化参数,默认为false,当该值为true,pageNum<=0默认查询第一页,pageNum>pages时会查询最后一页
pagehelper.reasonable=true
# 支持通过 Mapper接口参数来传递分页参数
pagehelper.support-methods-arguments=true
pagehelper.params=count=countSql
# 默认不会进行count查询,如果你想在分页查询时进行count查询,以及使用更强大的PageInfo类,你需要设置该参数为true。
pagehelper.row-bounds-with-count=true
# RowBounds中offset,limit当pageNum、pageSize来使用
pagehelper.offset-as-page-num=true
# 想通过控制参数查询全部的结果,pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果
pagehelper.page-size-zero=true

参数的具体说明,可以参考官网:

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md#3-%E5%88%86%E9%A1%B5%E6%8F%92%E4%BB%B6%E5%8F%82%E6%95%B0%E4%BB%8B%E7%BB%8D

使用

接着就是使用了,这里贴出官方的例子,只贴出来推荐的方式:

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

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

在你需要进行分页的 MyBatis 查询方法前调用 PageHelper.startPage 静态方法即可,紧跟在这个方法后的第一个MyBatis 查询方法会被进行分页。

分页原理:PageHelper.startPage会拦截下一个sql,也就是userMapper.selectIf(1)的SQL。并且根据当前数据库的语法,把这个SQL改造成一个高性能的分页SQL,同时还会查询该表的总行数,具体可以看SQL日志。
PageHelper.startPage和userMapper.selectIf(1)最好紧跟在一起,中间不要有别的逻辑,否则可能出BUG。
Page<User> page:相当于一个list集合,selectIf(1)方法查询完成后,会给page对象的相关参数赋值。

我使用的方法:

/**
 *
 * @param qry
 * @param pageable
 * @return
 */
public PageInfo<TempUrl> testPage(TempQry qry, Pageable pageable) {
    int page = pageable.getPageNum();
    int size = pageable.getPageSize();

	//项目里自定义的pojo类
    TempUrl tempUrl = new TempUrl();
    tempUrl.setName(qry.getName());
    //重点就是这句
    PageHelper.startPage(page, size);
    //这里调用的是mybatis的mapper
    List<TempUrl> tempUrls = tempUrlDao.selectByPage(tempUrl);
    //用PageInfo包装了一下(官方推荐)
    return new PageInfo<>(tempUrls);
}

这里我使用了PageInfo类;(官方说,这个类更全面)
假设不用PageInfo类的话,那么就需要对结果进行强转:

实际返回的结果list类型是Page,如果想取出分页信息,需要强制转换为Page
拿上面的例子说,虽然tempUrls是个List<TempUrl>,但是经过组件Pagehelper,处理后,就变成了Page<TempUrl>

可以进行下面这样的处理

int total = ((Page) tempUrls).getTotal());

但是我不喜欢强转,而且官方视乎更加建议使用PageInfo

官方举的例子

注意事项

第0页为什么和第1页结果一样?

传递如下参数,结果却一样:

pageNum=0&PageSize=10
pageNum=1&PageSize=10

这是因为配置中了pagehelper.reasonable=true。所以会把第0页,当第一页处理,并且也会把超过最后一页的当做最后一页来处理。

Pageable 是自己定义的

原来是想使用spring data jpa中的pageable,但是项目的controller,并不是spring那套体系,而是公司自己重写的一套泛化调用,所以没办法,springMVC用不了,就自己定义。

import lombok.Data;

@Data
public class Pageable {

    /**
     * 页号
     */
    private int pageNum;

    /**
     * 一页条数
     */
    private int pageSize;

    /**
     * 总页数
     */
    private int pages;

    /**
     * 总记录数
     */
    private int total;

}

总结

  1. Pagehelper分页本质,其实就是该组件对SQL进行拦截,然后改造成一个可以支持分页的SQL。有点类似AOP,说不定,底层就是AOP来做的(个人猜测)。
  2. 推荐使用静态方法PageHelper.startPage(1, 10);来调用。
  3. PageHelper.startPage(1, 10); 后面要紧跟着mybatis的mapper的调用方法,因为使用不当很可能会造成不安全的分页

什么时候会导致不安全的分页?

PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。

只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelperfinally 代码段中自动清除了 ThreadLocal 存储的对象。

如果代码在进入 Executor 前发生异常,就会导致线程不可用,这属于人为的 Bug(例如接口方法和 XML 中的不匹配,导致找不到 MappedStatement 时), 这种情况由于线程不可用,也不会导致 ThreadLocal 参数被错误的使用。

但是如果你写出下面这样的代码,就是不安全的用法:

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

这种情况下由于 param1 存在 null 的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。

上面这个代码,应该写成下面这个样子:

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

这种写法就能保证安全。

如果你对此不放心,你可以手动清理 ThreadLocal 存储的分页参数,可以像下面这样使用:

List<User> list;
if(param1 != null){
    PageHelper.startPage(1, 10);
    try{
        list = userMapper.selectAll();
    } finally {
        PageHelper.clearPage();
    }
} else {
    list = new ArrayList<User>();
}

这么写很不好看,而且没有必要。


参考地址:

SpringBoot 使用 MyBatis PageHelper 分页插件

SpringBoot+Mybatis+Pagehelper分页

官方使用方法

 类似资料: