PageHelper分页插件
文章目录
- 1、使用方式
- 2、原理
- 3、注意事项
1、使用方式
引入 PageHelper 插件
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.11</version>
</dependency>
在 mybatis-config.xml
中添加插件配置
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
在查询方法中使用 PageHelper
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
public List<YourEntity> selectPage(int pageNum, int pageSize) {
// 开启分页
PageHelper.startPage(pageNum, pageSize);
// 执行查询
List<YourEntity> list = yourMapper.selectAll();
// 通过 PageInfo 封装分页信息
PageInfo<YourEntity> pageInfo = new PageInfo<>(list);
return list;
}
2、原理
我们可以看到,使用pageHelper插件时使用主要分三步:( 1.开启分页、2.执行查询、3.处理结果 ),那么原理是怎么样的呢?
PageHelper.startPage(pageNum, pageSize);
开启分页后,最终会调用如下重载方法
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,将页面对象存储到 ThreadLocal 当中。
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
/**
* 设置 Page 参数
*
* @param page
*/
protected static void setLocalPage(Page page) {
LOCAL_PAGE.set(page);
}
在执行查询时会被 PageInterceptor 拦截器拦截,拦截方法为 intercept
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
//由于逻辑关系,只会进入一次
if (args.length == 4) {
//4 个参数时
boundSql = ms.getBoundSql(parameter); // 拦截原始 sql
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
checkDialectExists();
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
//查询总数
Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
//处理查询总数,返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
// 进行分页查询,并将原始sql作为参数传递到此方法中
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
// 分页查询后,处理分页结果,拦截器中直接 return 该方法的返回值
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
if(dialect != null){
// 查询的后置操作,清除 ThreadLocal 本地变量
dialect.afterAll();
}
}
}
执行分页查询方法
public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql, CacheKey cacheKey) throws SQLException {
//判断是否需要进行分页查询
if (dialect.beforePage(ms, parameter, rowBounds)) {
//生成分页的缓存 key
CacheKey pageKey = cacheKey;
//处理参数对象
parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
//调用方言获取分页 sql
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
//设置动态参数
for (String key : additionalParameters.keySet()) {
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行分页查询
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
}
执行分页查询方法时,还会调用 AbstractHelperDialect 的 getPageSql 方法获取分页 sql
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
String sql = boundSql.getSql();
// 从ThreadLocal中获取分页对象
Page page = getLocalPage();
//支持 order by
String orderBy = page.getOrderBy();
if (StringUtil.isNotEmpty(orderBy)) {
pageKey.update(orderBy);
sql = OrderByParser.converToOrderBySql(sql, orderBy);
}
if (page.isOrderByOnly()) {
return sql;
}
// 当前类为抽象类,会调用具体方言的 getPageSql 方法,如 mysql
return getPageSql(sql, page, pageKey);
}
分页查询执行完成后会执行 方法,处理结果
public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
Page page = getLocalPage();
if (page == null) {
return pageList;
}
page.addAll(pageList);
if (!page.isCount()) {
page.setTotal(-1);
} else if ((page.getPageSizeZero() != null && page.getPageSizeZero()) && page.getPageSize() == 0) {
page.setTotal(pageList.size());
} else if(page.isOrderByOnly()){
page.setTotal(pageList.size());
}
return page;
}
分页查询结果处理完成后会执行 afterAll() 清除 ThreadLocal 中设置的分页参数
public void afterAll() {
//这个方法即使不分页也会被执行,所以要判断 null
AbstractHelperDialect delegate = autoDialect.getDelegate();
if (delegate != null) {
delegate.afterAll();
autoDialect.clearDelegate();
}
// 清除分页对象
clearPage();
}
public static void clearPage() {
LOCAL_PAGE.remove();
}
3、注意事项
由于我们执行一次分页查询后就会清空 ThreadLocal 中的 Page 对象,所以如果一个方法中如果要进行两次分页查询就需要设置两次分页参数
// 开启分页
PageHelper.startPage(pageNum, pageSize);
// 执行查询
List<YourEntity> list = yourMapper.selectAll();
// 开启分页
PageHelper.startPage(pageNum, pageSize);
// 执行查询
List<YourEntity> list = yourMapper.selectAll();
另外,执行分页查询前会先执行一次 count 查询,这样似乎有利于性能提升、避免无效查询,同时还可以获得总记录数。
如果不想执行 count 查询,创建 Page 对象时可以设置 count 变量为 false
public class Page<E> extends ArrayList<E> implements Closeable {
private static final long serialVersionUID = 1L;
/**
* 页码,从1开始
*/
private int pageNum;
/**
* 页面大小
*/
private int pageSize;
/**
* 起始行
*/
private int startRow;
/**
* 末行
*/
private int endRow;
/**
* 总数
*/
private long total;
/**
* 总页数
*/
private int pages;
/**
* 包含count查询
*/
private boolean count = true;
/**
* 分页合理化
*/
private Boolean reasonable;
/**
* 当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
*/
private Boolean pageSizeZero;
/**
* 进行count查询的列名
*/
private String countColumn;
/**
* 排序
*/
private String orderBy;
/**
* 只增加排序
*/
private boolean orderByOnly;
}
PageHelper 的使用和原理非常简单,平时注意即可。