pagehelper 分页插件
public class PageHelper extends PageMethod implements Dialect, BoundSqlInterceptor.Chain {
PageMethod
分页静态方法
public abstract class PageMethod {
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
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;
}
public static void orderBy(String orderBy) {
Page<?> page = getLocalPage();
if (page != null) {
page.setOrderBy(orderBy);
} else {
page = new Page();
page.setOrderBy(orderBy);
page.setOrderByOnly(true);
setLocalPage(page);
}
}
public static <E> Page<E> offsetPage(int offset, int limit, boolean count) {
Page<E> page = new Page<E>(new int[]{offset, limit}, count);
//当已经执行过orderBy的时候
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
setLocalPage(page);
return page;
}
}
分页参数存储依赖于
- Page 分页对象
- ThreadLocal 保存分页对象
BoundSqlInterceptor.Chain
BoundSqlInterceptor 是处理 BoundSql 的
BoundSqlInterceptor.Chain 则是一个处理器链
public interface BoundSqlInterceptor {
BoundSql boundSql(Type type, BoundSql boundSql, CacheKey cacheKey, Chain chain);
}
enum Type {
/**
* 原始SQL,分页插件执行前,先执行这个类型
*/
ORIGINAL,
/**
* count SQL,第二个执行这里
*/
COUNT_SQL,
/**
* 分页 SQL,最后执行这里
*/
PAGE_SQL
}
interface Chain {
Chain DO_NOTHING = new Chain() {
@Override
public BoundSql doBoundSql(Type type, BoundSql boundSql, CacheKey cacheKey) {
return boundSql;
}
};
BoundSql doBoundSql(Type type, BoundSql boundSql, CacheKey cacheKey);
}
Dialect
数据库分页方言
public interface Dialect {
// 跳过 count 和 分页查询, true 跳过,返回默认查询结果,false 执行分页查询
boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds);
// 执行分页前,返回 true 会进行 count 查询,false 会继续下面的 beforePage 判断
boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds);
// 生成 count 查询 sql
String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey);
// 执行完 count 查询后,true 继续分页查询,false 直接返回
boolean afterCount(long count, Object parameterObject, RowBounds rowBounds);
// 处理查询参数对象
Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey);
// 执行分页前,返回 true 会进行分页查询,false 会返回默认查询结果
boolean beforePage(MappedStatement ms, Object parameterObject, RowBounds rowBounds);
// 生成分页查询 sql
String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey);
// 分页查询后,处理分页结果,拦截器中直接 return 该方法的返回值
Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds);
// 完成所有任务后
void afterAll();
// 设置参数
void setProperties(Properties properties);
}
PageHelper
public class PageHelper {
// 分页参数信息
private PageParams pageParams;
// 分页方言
private PageAutoDialect autoDialect;
// BoundSql 拦截
private PageBoundSqlInterceptors pageBoundSqlInterceptors;
}
// 是否跳过分页
public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
Page page = pageParams.getPage(parameterObject, rowBounds);
// 没有分页参数则跳过分页
if (page == null) {
return true;
} else {
//设置默认的 count 列
if (StringUtil.isEmpty(page.getCountColumn())) {
page.setCountColumn(pageParams.getCountColumn());
}
// ?
autoDialect.initDelegateDialect(ms, page.getDialectClass());
return false;
}
}
// 分页查询后处理分页结果, 一般就是转换 List 为分页对象, 填充分页参数数据
@Override
public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
//这个方法即使不分页也会被执行,所以要判断 null
AbstractHelperDialect delegate = autoDialect.getDelegate();
if (delegate != null) {
return delegate.afterPage(pageList, parameterObject, rowBounds);
}
return pageList;
}
// 完成所有任务后
@Override
public void afterAll() {
//这个方法即使不分页也会被执行,所以要判断 null
AbstractHelperDialect delegate = autoDialect.getDelegate();
if (delegate != null) {
// 暂无实现
delegate.afterAll();
// 也有 ThreadLocal 的清理
autoDialect.clearDelegate();
}
// 清除 ThreadLocal 的分页参数
clearPage();
}
@Override
public BoundSql doBoundSql(BoundSqlInterceptor.Type type, BoundSql boundSql, CacheKey cacheKey) {
Page<Object> localPage = getLocalPage();
BoundSqlInterceptor.Chain chain = localPage != null ? localPage.getChain() : null;
if (chain == null) {
BoundSqlInterceptor boundSqlInterceptor = localPage != null ? localPage.getBoundSqlInterceptor() : null;
BoundSqlInterceptor.Chain defaultChain = pageBoundSqlInterceptors != null ? pageBoundSqlInterceptors.getChain() : null;
if (boundSqlInterceptor != null) {
chain = new BoundSqlInterceptorChain(defaultChain, Arrays.asList(boundSqlInterceptor));
} else if (defaultChain != null) {
chain = defaultChain;
}
if (chain == null) {
chain = DO_NOTHING;
}
if (localPage != null) {
localPage.setChain(chain);
}
}
return chain.doBoundSql(type, boundSql, cacheKey);
}
@Override
public void setProperties(Properties properties) {
setStaticProperties(properties);
pageParams = new PageParams();
autoDialect = new PageAutoDialect();
pageBoundSqlInterceptors = new PageBoundSqlInterceptors();
pageParams.setProperties(properties);
autoDialect.setProperties(properties);
pageBoundSqlInterceptors.setProperties(properties);
//20180902新增 aggregateFunctions, 允许手动添加聚合函数(影响行数)
CountSqlParser.addAggregateFunctions(properties.getProperty("aggregateFunctions"));
}
PageInterceptor
分页插件,拦截 Executor 执行器的 query 方法
@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 {
public class PageInterceptor implements Interceptor {
private static final Log log = LogFactory.getLog(PageInterceptor.class);
private static boolean debug = false;
protected Cache<String, MappedStatement> msCountMap = null;
protected CountMsIdGen countMsIdGen = CountMsIdGen.DEFAULT;
private volatile Dialect dialect;
private String countSuffix = "_COUNT";
private String default_dialect_class = "com.github.pagehelper.PageHelper";
public PageInterceptor() {
// Banner 打印
}
}
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();
//对 boundSql 的拦截处理
if (dialect instanceof BoundSqlInterceptor.Chain) {
// ? 一般是做啥呢
boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);
}
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
// false 需要分页
if (!dialect.skip(ms, parameter, rowBounds)) {
//开启debug时,输出触发当前分页执行时的PageHelper调用堆栈
// 如果和当前调用堆栈不一致,说明在启用分页后没有消费,当前线程再次执行时消费,调用堆栈显示的方法使用不安全
debugStackTraceLog();
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
//查询总数
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);
}
// 转换分页对象
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
if (dialect != null) {
// finally 执行清除分页状态信息, 典型 ThreadLocal 的数据
dialect.afterAll();
}
}
}
ExecutorUtil
执行 count 和 分页查询
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));
}
//对 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);
}
}
executeAutoCount 和 pageQuery 差不多
是可以自定义 count 方法的
分页/count SQL
public abstract class AbstractHelperDialect extends AbstractDialect implements Constant {
public abstract String getPageSql(String sql, Page page, CacheKey pageKey);
}
MySQL 的实现
public class MySqlDialect extends AbstractHelperDialect {
@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
if (page.getStartRow() == 0) {
sqlBuilder.append("\n LIMIT ? ");
} else {
sqlBuilder.append("\n LIMIT ?, ? ");
}
return sqlBuilder.toString();
}
}
分页需要参数,参数处理
pageQuery 中调用了处理参数对象
public abstract class AbstractHelperDialect extends AbstractDialect implements Constant {
@Override
public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {
//处理参数
Page page = getLocalPage();
//如果只是 order by 就不必处理参数
if (page.isOrderByOnly()) {
return parameterObject;
}
Map<String, Object> paramMap = null;
if (parameterObject == null) {
paramMap = new HashMap<String, Object>();
} else if (parameterObject instanceof Map) {
//解决不可变Map的情况
paramMap = new HashMap<String, Object>();
paramMap.putAll((Map) parameterObject);
} else {
paramMap = new HashMap<String, Object>();
// sqlSource为ProviderSqlSource时,处理只有1个参数的情况
if (ms.getSqlSource() instanceof ProviderSqlSource) {
String[] providerMethodArgumentNames = ExecutorUtil.getProviderMethodArgumentNames((ProviderSqlSource) ms.getSqlSource());
if (providerMethodArgumentNames != null && providerMethodArgumentNames.length == 1) {
paramMap.put(providerMethodArgumentNames[0], parameterObject);
paramMap.put("param1", parameterObject);
}
}
//动态sql时的判断条件不会出现在ParameterMapping中,但是必须有,所以这里需要收集所有的getter属性
//TypeHandlerRegistry可以直接处理的会作为一个直接使用的对象进行处理
boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
MetaObject metaObject = MetaObjectUtil.forObject(parameterObject);
//需要针对注解形式的MyProviderSqlSource保存原值
if (!hasTypeHandler) {
for (String name : metaObject.getGetterNames()) {
paramMap.put(name, metaObject.getValue(name));
}
}
//下面这段方法,主要解决一个常见类型的参数时的问题
if (boundSql.getParameterMappings() != null && boundSql.getParameterMappings().size() > 0) {
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
String name = parameterMapping.getProperty();
if (!name.equals(PAGEPARAMETER_FIRST)
&& !name.equals(PAGEPARAMETER_SECOND)
&& paramMap.get(name) == null) {
if (hasTypeHandler
|| parameterMapping.getJavaType().equals(parameterObject.getClass())) {
paramMap.put(name, parameterObject);
break;
}
}
}
}
}
return processPageParameter(ms, paramMap, page, boundSql, pageKey);
}
}