PageHelper关闭count语句优化
目錄
- 前言
- 分析
- 源碼
- 總結
前言
PageHelper是一個很好用的分頁插件,在這個插件中使用分頁會執行兩句sql
1、count語句的sql,因為分頁需要總數
2、分頁語句,使用分頁參數進行服務端分頁的sql
分析
我們在調用分頁方法時正常會調用
PageHelper.startPage(1,10);但是如果我們不需要分頁總數時可以關閉count的sql語句查詢,使用重載的方法即可
/*** 開始分頁** @param pageNum 頁碼* @param pageSize 每頁顯示數量*/public static <E> Page<E> startPage(int pageNum, int pageSize) {return startPage(pageNum, pageSize, DEFAULT_COUNT);}/*** 開始分頁** @param pageNum 頁碼* @param pageSize 每頁顯示數量* @param count 是否進行count查詢*/public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {return startPage(pageNum, pageSize, count, null, null);}調用
PageHelper.startPage(1,10,false);本文要講的是在PageHelper里面執行count時會對sql語句進行優化,而這個解析的過程中可能會存在一些解析失敗的情況,導致sql語句報錯
例如執行
select a.* from (table1 a,table2 b) where a.id = b.id;正常情況下我們會覺得count的語句會是這樣的
select count(*) from (select a.* from (table1 a,table2 b) where a.id = b.id) table;但是實際上PageHelper會對count語句做一個優化
在我們報錯的日志看sql是這樣的
select count(0) from (select a.* from (table1 a table2 b) where a.id = b.id) table;我們會發現table1 a,table2 b之間的逗號不見了,這就是解析失敗的場景
那么如何才能關閉sql語句的解析優化呢,我們從源碼開始了解下
源碼
我們從PageHelper的核心開始看,先看PageInterceptor類,這個類是使用的Mybatis插件來實現的,插件機制這里不做分析,可以看這篇
@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";@Overridepublic 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);}}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 {dialect.afterAll();}}看核心代碼處的count方法
private Long count(Executor executor, MappedStatement ms, Object parameter,RowBounds rowBounds, ResultHandler resultHandler,BoundSql boundSql) throws SQLException {String countMsId = ms.getId() + countSuffix;Long count;//先判斷是否存在手寫的 count 查詢MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);if (countMs != null) {count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);} else {countMs = msCountMap.get(countMsId);//自動創建if (countMs == null) {//根據當前的 ms 創建一個返回值為 Long 類型的 mscountMs = MSUtils.newCountMappedStatement(ms, countMsId);msCountMap.put(countMsId, countMs);}// 核心代碼count = ExecutorUtil.executeAutoCount(dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);}return count;}看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 查詢的緩存 keyCacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);//調用方言獲取 count sql// 核心代碼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));}//執行 count 查詢Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);Long count = (Long) ((List) countResultList).get(0);return count;}看getCountSql方法,到了PageHelper的getCountSql
@Overridepublic String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {return autoDialect.getDelegate().getCountSql(ms, boundSql, parameterObject, rowBounds, countKey);}AbstractHelperDialect的getCountSql方法
public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {Page<Object> page = getLocalPage();String countColumn = page.getCountColumn();if (StringUtil.isNotEmpty(countColumn)) {return countSqlParser.getSmartCountSql(boundSql.getSql(), countColumn);}// 核心代碼return countSqlParser.getSmartCountSql(boundSql.getSql());}調用了countSqlParser.getSmartCountSql
public String getSmartCountSql(String sql) {return getSmartCountSql(sql, "0"); } public String getSmartCountSql(String sql, String name) {//解析SQLStatement stmt = null;//特殊sql不需要去掉order by時,使用注釋前綴// 核心代碼if(sql.indexOf(KEEP_ORDERBY) >= 0){return getSimpleCountSql(sql);}try {stmt = CCJSqlParserUtil.parse(sql);} catch (Throwable e) {//無法解析的用一般方法返回count語句return getSimpleCountSql(sql);}Select select = (Select) stmt;SelectBody selectBody = select.getSelectBody();try {//處理body-去order byprocessSelectBody(selectBody);} catch (Exception e) {//當 sql 包含 group by 時,不去除 order byreturn getSimpleCountSql(sql);}//處理with-去order byprocessWithItemsList(select.getWithItemsList());//處理為count查詢sqlToCount(select, name);String result = select.toString();return result;}看到了上面的核心代碼
if(sql.indexOf(KEEP_ORDERBY) >= 0){return getSimpleCountSql(sql);}try {stmt = CCJSqlParserUtil.parse(sql);} catch (Throwable e) {//無法解析的用一般方法返回count語句return getSimpleCountSql(sql);}KEEP_ORDERBY定義
public static final String KEEP_ORDERBY = "/*keep orderby*/";也就是當sql中包含/*keep orderby*/字符串時會走getSimpleCountSql
public String getSimpleCountSql(final String sql, String name) {StringBuilder stringBuilder = new StringBuilder(sql.length() + 40);stringBuilder.append("select count(");stringBuilder.append(name);stringBuilder.append(") from (");stringBuilder.append(sql);stringBuilder.append(") tmp_count");return stringBuilder.toString();}getSimpleCountSql也就是簡單的對sql做了個count的拼接
而另外的情況就會調用CCJSqlParserUtil.parse(sql),CCJSqlParserUtil類是jsqlparser-1.2.jar的一個工具類,用于對sql進行解析的,也就是會把我們的sql做一個優化解析。
但是例如我們上述這種場景,我們的sql在mysql服務端是可以正常執行的,但是被優化后就不能正常執行了,這個時候我們只要在我們的sql中加上/*keep orderby*/即可使用簡單的count拼接了
總結
PageHelper在解析優化count語句時可能不能理解我們的語義,導致解析后不是我們需要的sql,這個時候我們就只能想辦法關閉解析優化了
/*keep orderby*/是一個注釋,實際上不會對我們的sql執行造成影響,并且這種方法也不是PageHelper提供給我們的關閉count語句優化的方法,但是基于源碼,我們這樣做確實可以解決問題
可以看到PageHelper的源碼看起來還是相對簡單的,并且注釋也是中文的,看起來并不費勁。這里就是對自己使用過程中出現問題解決方案的一個總結了
總結
以上是生活随笔為你收集整理的PageHelper关闭count语句优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RGB/YUV/YIQ 颜色空间
- 下一篇: dcn网络与公网_DCN网络安全分析