Table of contents
1 Introduction
PageHelper is a paging plug-in provided by mybatis, which PageHelper.startPage(pageNo,pageLimit)
can help us realize paging through it. It currently supports six databases: Oracle, Mysql, MariaDB, SQLite, Hsqldb, and PostgreSQL.
pom dependencies:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.1</version>
</dependency>
yml configuration:
pagehelper:
helperDialect: mysql #数据库类型,不指定的话会解析 datasource.url进行配置
supportMethodsArguments: true
params: count=countSql
Example usage:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* 依据用户昵称进行模糊分页查询
*
* @param name
* @param page
* @param limit
* @return
*/
public PageInfo<User> findPageUsersByName(String name, int page, int limit) {
PageHelper.startPage(page, limit);
List<User> users = userMapper.selectByName(name);
PageInfo<User> pageUsers = new PageInfo<>(users);
return pageUsers;
}
}
2. Basic process
1. PageHelper registers PageInterceptor, an interceptor that handles paging and count, with Mybatis
2. Put the paging-related parameters into ThreadLcoal through the PageHelper.startPage() method
3. Mybatis will call the interceptor during SQL execution
- 3.1. Build count SQL based on query SQL
- 3.2. Take out the paging information from ThreadLcoal, and splicing limit after the query SQL ?, ?
- 3.3. Clear ThreadLcoal
4. Use Page to create PageInfo object
3. Source code analysis
3.1. Store paging parameters
Look directly at PageHelper.startPage, the startPage() method will build a Page object, store paging-related parameters and settings, and finally call setLocalPage(page) to put it into ThreadLocal. Since ThreadLocal will be removed after each query, a mapper query corresponds to a PageHelper.startPage.
The principle of ThreadLocal can be seen in this ThreadLocal source code and its memory leak problem
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;
}
3.2. Transform SQL
PageInterceptor implements the org.apache.ibatis.plugin.Interceptor interface. MyBatis underlying query is actually calling Executor#query with the help of SqlSession. Mybatis will call this interceptor when executing the query method (method = "query").
/**
* Mybatis - 通用分页拦截器
*/
@SuppressWarnings({
"rawtypes", "unchecked"})
@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 {
// 默认为 Pagehelper
private volatile Dialect dialect;
// count 方法的后缀
private String countSuffix = "_COUNT";
// count 查询的缓存,只用于
// 本例中 key 为 com.example.pagehelper.dao.UserMapper.selectUsers_COUNT
protected Cache<String, MappedStatement> msCountMap = null;
//
private String default_dialect_class = "com.github.pagehelper.PageHelper";
...
}
access PageInterceptor.intercept()
method,
@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 个参数时
// 拿到原始的查询 SQL
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
checkDialectExists();
//对 boundSql 的拦截处理
// 实际什么都没做,原样返回了
if (dialect instanceof BoundSqlInterceptor.Chain) {
boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);
}
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
// 查询总数
// 见 PageInterceptor.count()
Long count = count(executor, ms, parameter, rowBounds, null, boundSql);
//处理查询总数,返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
// 执行分页查询
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
// 将count、分页 信息放入 ThreadLocal
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
if(dialect != null){
dialect.afterAll();
}
}
}
count
PageInterceptor.count()
private Long count(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
// countMsId = "com.example.pagehelper.dao.UserMapper.selectUsers_COUNT"
String countMsId = ms.getId() + countSuffix;
Long count;
//先判断是否存在手写的 count 查询
MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
if (countMs != null) {
// 直接执行手写的 count 查询
count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
} else {
// 先从缓存中查
if (msCountMap != null) {
countMs = msCountMap.get(countMsId);
}
// 缓存中没有,然后自动创建,并放入缓存
if (countMs == null) {
//根据当前的 ms 创建一个返回值为 Long 类型的 ms
countMs = MSUtils.newCountMappedStatement(ms, countMsId);
if (msCountMap != null) {
// 放入缓存
msCountMap.put(countMsId, countMs);
}
}
// 执行 count 查询
count = ExecutorUtil.executeAutoCount(this.dialect, executor, countMs, parameter,
boundSql, rowBounds, resultHandler);
}
return count;
}
ExecutorUtil.executeAutoCount()
public static Long executeAutoCount(Dialect dialect, Executor executor, MappedStatement countMs,
Object parameter, BoundSql boundSql,
RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
//创建 count 查询的缓存 key
CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
//调用方言获取 count sql:SELECT count(0) FROM user
// 见 PageHelper.getCountSql()
String countSql = dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);
//countKey.update(countSql);
BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
//当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
for (String key : additionalParameters.keySet()) {
countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//对 boundSql 的拦截处理
if (dialect instanceof BoundSqlInterceptor.Chain) {
countBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.COUNT_SQL, countBoundSql, countKey);
}
//执行 count 查询
Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
Long count = (Long) ((List) countResultList).get(0);
return count;
}
3.3. Pagination query
ExecutorUtil.pageQuery
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,这里是重点,是添加 limit 的地方
// pageSql = select id, name from user LIMIT ?, ?
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));
}
//对 boundSql 的拦截处理
if (dialect instanceof BoundSqlInterceptor.Chain) {
pageBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.PAGE_SQL, pageBoundSql, pageKey);
}
//执行分页查询
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
}
3.4. Use Page to create PageInfo object
Finally, use PageInfo<User> pageUsers = new PageInfo<>(users)
to get the paging information