PageHelper實(shí)現(xiàn)分頁原理
場(chǎng)景
PageHelper.startPage(page, pageSize);看到這句話后開始思考為何它可以直接實(shí)現(xiàn)分頁邏輯,它是怎么實(shí)現(xiàn)的?為何在數(shù)據(jù)庫查詢的前面加上了這句話就可以實(shí)現(xiàn)分頁,里面到底發(fā)生了什么?
介紹
com.github.pagehelper.PageHelper是一個(gè)開源的分頁源碼工具
<dependency>
<groupId>com.github.pagehelpergroupId>
<artifactId>pagehelper-spring-boot-starterartifactId>
<version>1.2.12version>
dependency>配置文件中也需要配置
# 分頁插件配置
pagehelper:
helperDialect: mysql
supportMethodsArguments: true源碼分析
分頁參數(shù)的傳遞
public static Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
Page page = new Page(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
Page oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
setLocalPage(page);
return page;
} 這段代碼中第一行是寫入?yún)?shù),第二行和第三行都是null,可以不考慮
主要的代碼是這兩行:
getLocalPage();
setLocalPage(page);
這里用到了一個(gè)ThreadLocal;
ThreadLocal,很多地方叫做線程本地變量,為變量在每個(gè)線程中都創(chuàng)建了一個(gè)副本,那么每個(gè)線程可以訪問自己內(nèi)部的副本變量。
展示到這就看不到其他了,這就很尷尬了,然后繼續(xù)看jar包中的代碼,然后發(fā)現(xiàn)了PageInterceptor這個(gè)類,分頁攔截器,前面已經(jīng)設(shè)置了分頁參數(shù)在本地變量中,然后就該是調(diào)用了,很大可能就是在這個(gè)攔截器中進(jìn)行實(shí)現(xiàn)。

PageInterceptor 實(shí)現(xiàn)Mybatis的Interceptor 接口,進(jìn)行攔截
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();
List resultList;
if (!this.dialect.skip(ms, parameter, rowBounds)) {
if (this.dialect.beforeCount(ms, parameter, rowBounds)) {
Long count = this.count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
if (!this.dialect.afterCount(count, parameter, rowBounds)) {
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 {
if (this.dialect != null) {
this.dialect.afterAll();
}
}
}然后進(jìn)入查看ExecutorUtil.pageQuery,看是怎么實(shí)現(xiàn)的:

查看分頁的sql語句,繼續(xù):
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
String sql = boundSql.getSql();
Page page = this.getLocalPage();
String orderBy = page.getOrderBy();
if (StringUtil.isNotEmpty(orderBy)) {
pageKey.update(orderBy);
sql = OrderByParser.converToOrderBySql(sql, orderBy);
}
return page.isOrderByOnly() ? sql : this.getPageSql(sql, page, pageKey);
}看到最后的三則表達(dá)式后后的getPageSql

進(jìn)入到MySqlDialect類的getPageSql方法進(jìn)行SQL封裝,根據(jù)page對(duì)象信息增加Limit。分頁的信息就是這么拼裝起來的
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(" LIMIT ? ");
} else {
sqlBuilder.append(" LIMIT ?, ? ");
}
return sqlBuilder.toString();
}總結(jié)
原來PageHelper的分頁功能是在我們執(zhí)行SQL語句之前動(dòng)態(tài)的將SQL語句拼接了分頁的語句,從而實(shí)現(xiàn)了從數(shù)據(jù)庫中分頁獲取的過程。
