mybatis-plus的分頁(yè)攔截器(PaginationInterceptor)分頁(yè)失敗問題及解決
背景
項(xiàng)目中使用到了mybatis-plus的3.0.5版本,分頁(yè)攔截器是PaginationInterceptor。
代碼如下:
@Transactional(readOnly = true) public void test() { int current =1; while (true) { final LambdaQueryWrapper<Foo> query = Condition.<Foo>lambda().gt(AppProject::getBusiId, 1); //傳入增長(zhǎng)頁(yè)數(shù)來查詢 Page<Foo> page = new Page<>(current++, 1000); Page<Foo> result = fooMapper.selectPage(page, query); //...業(yè)務(wù) if (result .getRecords() == null && || CollectionUtils.isEmpty(result .getRecords())) { break; } } }
現(xiàn)象
1.無法從while中跳出循環(huán)。
2.打印sql日志只打印了一次。
猜測(cè)
- 懷疑分頁(yè)失敗了。
- 由于只有一次日志,懷疑之后都走到了緩存。
證明過程
1.使用arthas看看sql是怎樣的
tt org.apache.ibatis.mapping.SqlSource getBoundSql '{params,returnObj,throwExp}'
拿出來的sql的確沒有LIMIT
- TODO 待修改
打開sql日志,也有觀察到Limit語(yǔ)句,雖然只出現(xiàn)了一次,但說明是有分頁(yè)的。
因此:猜測(cè)1不成立。
2.查看PaginationInterceptor
PaginationInterceptor攔截的是StatementHandler#prepare,在prepare方法執(zhí)行的時(shí)候,才會(huì)添加Limit語(yǔ)句。而在調(diào)用添加Limit語(yǔ)句之前,已經(jīng)拿了緩存中的數(shù)據(jù),不會(huì)真正執(zhí)行查詢了。
- 調(diào)用順序?yàn)椋?/li>
由上圖可以看出,當(dāng)生成cacheKey的時(shí)候,由于此時(shí)的boundSql還未被攔截,不含有Limit
語(yǔ)句,每次boundSql都一樣,導(dǎo)致cacheKey都一樣。
因此:
- 只有第一次查詢會(huì)走不到緩存,然后被prepare階段被攔截,添加Limit語(yǔ)句后查詢數(shù)據(jù)庫(kù)。將數(shù)據(jù)放入一級(jí)緩存中。
- 第二次查詢開始,由于有事務(wù),會(huì)嘗試?yán)靡患?jí)緩存中的數(shù)據(jù),且cacheKey一樣,緩存有對(duì)應(yīng)的數(shù)據(jù),就直接返回了緩存中的數(shù)據(jù)。
生成緩存key
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
優(yōu)先查一級(jí)緩存,無緩存則查數(shù)據(jù)庫(kù)
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }
添加Limit語(yǔ)句
/** * <p> * MYSQL 數(shù)據(jù)庫(kù)分頁(yè)語(yǔ)句組裝實(shí)現(xiàn) * </p> * * @author hubin * @since 2016-01-23 */ public class MySqlDialect implements IDialect { @Override public String buildPaginationSql(String originalSql, long offset, long limit) { StringBuilder sql = new StringBuilder(originalSql); sql.append(" LIMIT ").append(offset).append(StringPool.COMMA).append(limit); return sql.toString(); } }
問題出現(xiàn)原因
- PaginationInterceptor攔截時(shí)期太晚,導(dǎo)致雖然傳了不同的頁(yè)數(shù),但是提前生成的cacheKey一樣。
- 使用了事務(wù),導(dǎo)致用到同一sqlSession,走到了一級(jí)緩存。
解決方法
- 升級(jí)Mybatis-Plus,高版本的MybatisPlusInterceptor來分頁(yè)。(MybatisPlusInterceptor的攔截在cacheKey生成之前,可以避免上述問題)
- 不在事務(wù)中使用分頁(yè)查詢。
總結(jié)
分頁(yè)太深,查詢太慢,最后還是放棄了這種分頁(yè)查詢方式。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- Mybatis-Plus中分頁(yè)插件PaginationInterceptor的使用
- 解決mybatis-plus3.4.1分頁(yè)插件PaginationInterceptor和防止全表更新與刪除插件SqlExplainInterceptor過時(shí)失效問題
- Mybatis-plus新版本分頁(yè)失效PaginationInterceptor過時(shí)的問題
- Spring Boot集成MyBatis-Plus 自定義攔截器實(shí)現(xiàn)動(dòng)態(tài)表名切換功能
- mybatis-plus配置攔截器實(shí)現(xiàn)sql完整打印的代碼設(shè)計(jì)
- MyBatis-Plus攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限控制的方法
相關(guān)文章
idea2020.1設(shè)置多個(gè)spring boot的service啟動(dòng)的實(shí)現(xiàn)
這篇文章主要介紹了idea2020.1設(shè)置多個(gè)spring boot的service啟動(dòng),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Spring 注解編程模型相關(guān)知識(shí)詳解
這篇文章主要介紹了Spring 注解編程模型相關(guān)知識(shí)詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09淺析java中ArrayList與Vector的區(qū)別以及HashMap與Hashtable的區(qū)別
以下是對(duì)java中ArrayList與Vector的區(qū)別以及HashMap與Hashtable的區(qū)別進(jìn)行了詳細(xì)的解析。需要的朋友可以過來參考下2013-08-08詳解Mybatis極其(最)簡(jiǎn)(好)單(用)的一個(gè)分頁(yè)插件
這篇文章主要介紹了詳解Mybatis極其(最)簡(jiǎn)(好)單(用)的一個(gè)分頁(yè)插件,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2016-12-12SpringBoot集成nacos動(dòng)態(tài)刷新數(shù)據(jù)源的實(shí)現(xiàn)示例
這篇文章主要介紹了SpringBoot集成nacos動(dòng)態(tài)刷新數(shù)據(jù)源的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12如何通過properties文件配置web.xml中的參數(shù)
這篇文章主要介紹了如何通過properties文件配置web.xml中的參數(shù)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Java基于MySQL實(shí)現(xiàn)學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java基于MySQL實(shí)現(xiàn)學(xué)生管理系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01Java多線程下解決資源競(jìng)爭(zhēng)的7種方法詳解
這篇文章主要介紹了Java多線程下解決資源競(jìng)爭(zhēng)的7種方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08SpringCloud+SpringBoot項(xiàng)目搭建結(jié)構(gòu)層次的實(shí)例
這篇文章詳細(xì)介紹了SpringCloud項(xiàng)目的架構(gòu)層次及其搭建經(jīng)驗(yàn),包括Controller層、Service層、Repository層、Entity層、DTO層、Exception層等,通過文字和圖片的形式,幫助讀者理解如何組織和實(shí)現(xiàn)一個(gè)SpringBoot項(xiàng)目的不同層次2025-01-01