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
参数的具体说明,可以参考官网:
接着就是使用了,这里贴出官方的例子,只贴出来推荐的方式:
//第二种,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
。
传递如下参数,结果却一样:
pageNum=0&PageSize=10
pageNum=1&PageSize=10
这是因为配置中了pagehelper.reasonable=true
。所以会把第0页,当第一页处理,并且也会把超过最后一页的当做最后一页来处理。
原来是想使用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;
}
Pagehelper
分页本质,其实就是该组件对SQL进行拦截,然后改造成一个可以支持分页的SQL。有点类似AOP,说不定,底层就是AOP来做的(个人猜测)。PageHelper.startPage(1, 10);
来调用。PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。
只要你可以保证在 PageHelper
方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelper
在 finally
代码段中自动清除了 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 分页插件