MyBatis PageHelper分页插件使用指南
发布于
# java
PageHelper使用
maven引入
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
代码使用
// 设置分页参数,第一页,分页条数为10
PageHelper.startPage(1, 10);
// 开始查询
List<FastName> list = fastNameMapper.queryNameByTopicTitle();
// 将查询结果包装到PageInfo,PageInfo中包含了页码,查询结果,当前页码等信息
PageInfo pageInfo = new PageInfo<>(list);
or
PageInfo pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(
() -> fastNameMapper.queryNameByTopicTitle());
PageHelper原理
先看下刚刚的startPage
方法
public static <E> Page<E> startPage(int pageNum, int pageSize) {
return startPage(pageNum, pageSize, DEFAULT_COUNT);
}
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {
return startPage(pageNum, pageSize, count, (Boolean)null, (Boolean)null);
}
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
Page<E> page = new Page(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
// @核心代码
setLocalPage(page);
return page;
}
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();
protected static boolean DEFAULT_COUNT = true;
protected static void setLocalPage(Page page) {
// @核心代码
LOCAL_PAGE.set(page);
}
根据分页参数保存了一个Page设置到ThreadLocal中保存起来。
核心的类:PageInterceptor
@Intercepts({@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})
public class PageInterceptor implements Interceptor {
private volatile Dialect dialect;
private String countSuffix = "_COUNT";
protected Cache<String, MappedStatement> msCountMap = null;
private String default_dialect_class = "com.github.pagehelper.PageHelper";
@Override
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);
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);
}
}
// @1 核心代码1
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
if(dialect != null){
// @2 核心代码2
dialect.afterAll();
}
}
}
xxxxxx......
}
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);
}
}
@Override
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
return autoDialect.getDelegate().getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey);
}
# getPageSql方法
@Override
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
String sql = boundSql.getSql();
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;
}
// @核心代码
return getPageSql(sql, page, pageKey);
}
总结:流程大致为:
- 设置分页参数到ThreadLocal中
- 调用查询,基于插件(拦截器)拦截query方法,从ThreadLocal中获取分页参数,针对不同的数据库类型使用不同的数据库分页方案
- 根据结果包装返回PageInfo,这里注意的是调用了查询方法返回的List实际上是PageHelper的一个内部实现类Page,Page继承自ArrayList,保存了分页使用的参数
封装PageReult
import lombok.Data;
import java.io.Serializable;
@Data
public class Page implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 当前为第几页(可在入参指定),从1开始
*/
private int pageNum;
/**
* 每页的数据行数(可在入参指定)
*/
private int pageSize;
/**
* 是否获取总行数
* 入参指定为true的场合,表示本次查询总页数信息
* 返回值为true的场合,表示为本次返回值包含总页数信息
*/
private Boolean count;
/**
* 总页数
* 非入参,入参指定获取总行数的场合,返回值中可获取
*/
private Integer pages;
/**
* 总行数
* 非入参,入参指定获取总行数的场合,返回值中可获取
*/
private Long total;
/**
* 是否有下一页
*/
private Boolean hasNextPage;
public Page(){}
public Page(int pageNum, int pageSize, boolean count){
this.pageNum = pageNum;
this.pageSize = pageSize;
this.count = count;
}
}
import com.github.pagehelper.PageHelper;
import lombok.Data;
@Data
public class PageModel {
/**
* 分页信息对象
*/
private Page page;
public void startPage() {
PageHelper.startPage(page.getPageNum(), page.getPageSize(), page.getCount() == null ? false : page.getCount());
}
}
@Data
public class PageResult<T> {
/**
* 分页信息对象
*/
private Page page;
/**
* 结果集
*/
private List<T> list;
public PageResult(List<T> list){
initializePageResult(list, null);
}
public PageResult(List<T> list, PageInfo<T> pageInfo) {
initializePageResult(list, pageInfo);
}
private void initializePageResult(List<T> list, PageInfo<T> pageInfo) {
//封入结果集
this.list = list;
this.page = new Page();
if (pageInfo == null) {
pageInfo = new PageInfo<>(list);
}
//当前页码
page.setPageNum(pageInfo.getPageNum());
//每页的数据行数
page.setPageSize(pageInfo.getPageSize());
if (pageInfo.getTotal() > -1) {
//本次查询了总页数信息的场合
//标记为本次结果有总页数信息
page.setCount(true);
//总页数
page.setPages(pageInfo.getPages());
//总记录数
page.setTotal(pageInfo.getTotal());
//是否有下一页
page.setHasNextPage(pageInfo.isHasNextPage());
}
}
}