日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

PageHelper关闭count语句优化

發布時間:2023/12/16 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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语句优化的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。