PageHelper分页原理源码分析
Posted kuku_zhongzi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PageHelper分页原理源码分析相关的知识,希望对你有一定的参考价值。
一、PageHelper的配置加载及使用
1.1 配置
参考地址 https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
在 pom.xml 中添加如下依赖:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.1</version>
</dependency>
在 yml 配置文件中添加如下依赖:
pagehelper:
helperDialect: mysql #数据库类型,不指定的话会解析 datasource.url进行配置
supportMethodsArguments: true
params: count=countSql
1.2 启动加载流程
SpringBoot项目启动时pagehelper的加载流程
// 核心类 这里继承了myabtis的拦截器,会在项目启动时加载此类
public class PageInterceptor implements Interceptor
// 构造方法 读取配置输出banner
public PageInterceptor()
String bannerEnabled = System.getProperty("pagehelper.banner");
if (StringUtil.isEmpty(bannerEnabled))
bannerEnabled = System.getenv("PAGEHELPER_BANNER");
if (StringUtil.isEmpty(bannerEnabled) || Boolean.parseBoolean(bannerEnabled))
log.debug("...");
// 核心拦截方法
public Object intercept(Invocation invocation) throws Throwable
// 代码省略....
// 统计方法
private Long count(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException
// 代码省略....
// 读取配置,设置属性
public void setProperties(Properties properties)
this.msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
// 如果没有配置dialect属性的话,会默认实例化PageHelper类
String dialectClass = properties.getProperty("dialect");
if (StringUtil.isEmpty(dialectClass))
dialectClass = this.default_dialect_class;
try
// 实例化PageHelper
// default_dialect_class => com.github.pagehelper.PageHelper
Class<?> aClass = Class.forName(dialectClass);
this.dialect = (Dialect)aClass.newInstance();
catch (Exception var4)
throw new PageException(var4);
this.dialect.setProperties(properties);
String countSuffix = properties.getProperty("countSuffix");
if (StringUtil.isNotEmpty(countSuffix))
this.countSuffix = countSuffix;
进入com.github.pagehelper.PageHelper实例化过程
// PageHelper的设置属性方法...读取配置,设置属性
public void setProperties(Properties properties)
setStaticProperties(properties);
this.pageParams = new PageParams();
this.autoDialect = new PageAutoDialect();
this.pageBoundSqlInterceptors = new PageBoundSqlInterceptors();
this.pageParams.setProperties(properties);
// 这里会根据配置文件中的helperDialect属性进行实例化方言实现类
this.autoDialect.setProperties(properties);
this.pageBoundSqlInterceptors.setProperties(properties);
CountSqlParser.addAggregateFunctions(properties.getProperty("aggregateFunctions"));
进入com.github.pagehelper.page.PageAutoDialect方言配置
// 1.进入 PageAutoDialect的设置属性方法...读取配置,设置属性
public void setProperties(Properties properties)
this.initAutoDialectClass(properties);
String useSqlserver2012 = properties.getProperty("useSqlserver2012");
if (StringUtil.isNotEmpty(useSqlserver2012) && Boolean.parseBoolean(useSqlserver2012))
registerDialectAlias("sqlserver", SqlServer2012Dialect.class);
registerDialectAlias("sqlserver2008", SqlServerDialect.class);
this.initDialectAlias(properties);
// 读取方言配置
String dialect = properties.getProperty("helperDialect");
String runtimeDialect = properties.getProperty("autoRuntimeDialect");
if (StringUtil.isNotEmpty(runtimeDialect) && "TRUE".equalsIgnoreCase(runtimeDialect))
this.autoDialect = false;
this.properties = properties;
else if (StringUtil.isEmpty(dialect))
this.autoDialect = true;
this.properties = properties;
else
this.autoDialect = false;
this.delegate = instanceDialect(dialect, properties);
// 静态方法,PageAutoDialect加载时会先把以下所有配置加入变量dialectAliasMap
static
registerDialectAlias("hsqldb", HsqldbDialect.class);
registerDialectAlias("h2", HsqldbDialect.class);
registerDialectAlias("phoenix", HsqldbDialect.class);
registerDialectAlias("postgresql", PostgreSqlDialect.class);
registerDialectAlias("mysql", MySqlDialect.class);
registerDialectAlias("mariadb", MySqlDialect.class);
registerDialectAlias("sqlite", MySqlDialect.class);
registerDialectAlias("herddb", HerdDBDialect.class);
registerDialectAlias("oracle", OracleDialect.class);
registerDialectAlias("oracle9i", Oracle9iDialect.class);
registerDialectAlias("db2", Db2Dialect.class);
registerDialectAlias("as400", AS400Dialect.class);
registerDialectAlias("informix", InformixDialect.class);
registerDialectAlias("informix-sqli", InformixDialect.class);
registerDialectAlias("sqlserver", SqlServerDialect.class);
registerDialectAlias("sqlserver2012", SqlServer2012Dialect.class);
registerDialectAlias("derby", SqlServer2012Dialect.class);
registerDialectAlias("dm", OracleDialect.class);
registerDialectAlias("edb", OracleDialect.class);
registerDialectAlias("oscar", OscarDialect.class);
registerDialectAlias("clickhouse", MySqlDialect.class);
registerDialectAlias("highgo", HsqldbDialect.class);
registerDialectAlias("xugu", HsqldbDialect.class);
registerDialectAlias("impala", HsqldbDialect.class);
registerDialectAlias("firebirdsql", FirebirdDialect.class);
registerAutoDialectAlias("old", DefaultAutoDialect.class);
registerAutoDialectAlias("hikari", HikariAutoDialect.class);
registerAutoDialectAlias("druid", DruidAutoDialect.class);
registerAutoDialectAlias("tomcat-jdbc", TomcatAutoDialect.class);
registerAutoDialectAlias("dbcp", DbcpAutoDialect.class);
registerAutoDialectAlias("c3p0", C3P0AutoDialect.class);
registerAutoDialectAlias("default", DataSourceNegotiationAutoDialect.class);
// 2. 实例化具体方言
public static AbstractHelperDialect instanceDialect(String dialectClass, Properties properties)
if (StringUtil.isEmpty(dialectClass))
throw new PageException("使用 PageHelper 分页插件时,必须设置 helper 属性");
else
AbstractHelperDialect dialect;
try
// 这里会去dialectAliasMap取值然后获取对应的class
Class sqlDialectClass = resloveDialectClass(dialectClass);
if (!AbstractHelperDialect.class.isAssignableFrom(sqlDialectClass))
throw new PageException("使用 PageHelper 时,方言必须是实现 " + AbstractHelperDialect.class.getCanonicalName() + " 接口的实现类!");
// 实例化
dialect = (AbstractHelperDialect)sqlDialectClass.newInstance();
catch (Exception var4)
throw new PageException("初始化 helper [" + dialectClass + "]时出错:" + var4.getMessage(), var4);
dialect.setProperties(properties);
return dialect;
1.3 使用
// 开启分页
PageUtils.startPage(1,10);
/**
* 开始分页
*
* @param pageNum 页码
* @param pageSize 每页显示数量
* @param count 是否进行count查询
* @param reasonable 分页合理化,null时用默认配置
* @param pageSizeZero true且pageSize=0时返回全部结果,false时分页,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);
// 这里会通过pageNum和pageSize计算起始行
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
//当已经执行过orderBy的时候
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly())
page.setOrderBy(oldPage.getOrderBy());
//把当前分页参数保存到ThreadLocal
setLocalPage(page);
return page;
至此,分页前的准备已经做好了,接着进行正常的查询操作。
二、分页流程
2.3 PageInterceptor拦截器
开启分页之后,所有的select请求都会被PageInterceptor捕获,从而进行分页
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)
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
else
cacheKey = (CacheKey)args[4];
boundSql = (BoundSql)args[5];
// 检查方言是否存在
this.checkDialectExists();
if (this.dialect instanceof Chain)
// 对原始sql语句进行处理
boundSql = ((Chain)this.dialect).doBoundSql(Type.ORIGINAL, boundSql, cacheKey);
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!this.dialect.skip(ms, parameter, rowBounds))
//判断是否需要进行 count 查询
if (this.dialect.beforeCount(ms, parameter, rowBounds))
//查询总数
Long count = this.count(executor, ms, parameter, rowBounds, (ResultHandler)null, boundSql);
//处理查询总数,返回 true 时继续分页查询,false 时直接返回
if (!this.dialect.afterCount(count, parameter, rowBounds))
//当查询总数为 0 时,直接返回空的结果
Object var12 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);
return var12;
// 执行分页
resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
else
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
Object var16 = this.dialect.afterPage(resultList, parameter, rowBounds);
return var16;
finally
// 清空ThreadLocal变量
if (this.dialect != null)
this.dialect.afterAll();
进入到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))
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
else
// 参数处理,把查询条件转换成Map<String, Object>
// 并且会put两个新参数
// First_PageHelper和Second_PageHelper分别是前面的pageNum和PageSize
parameter = dialect.processParameterObject(ms, parameter, boundSql, cacheKey);
// sql处理 这里根据分页参数的起始行 添加LIMIT ?, ? 或者 LIMIT ?
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, bou以上是关于PageHelper分页原理源码分析的主要内容,如果未能解决你的问题,请参考以下文章
吃透Mybatis源码-通过分析Pagehelper源码来理解Mybatis的拦截器