mybatis-plus的分頁攔截器(PaginationInterceptor)分頁失敗問題及解決
背景
項(xiàng)目中使用到了mybatis-plus的3.0.5版本,分頁攔截器是PaginationInterceptor。
代碼如下:
@Transactional(readOnly = true)
public void test() {
int current =1;
while (true) {
final LambdaQueryWrapper<Foo> query = Condition.<Foo>lambda().gt(AppProject::getBusiId, 1);
//傳入增長頁數(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日志只打印了一次。
猜測
- 懷疑分頁失敗了。
- 由于只有一次日志,懷疑之后都走到了緩存。
證明過程
1.使用arthas看看sql是怎樣的
tt org.apache.ibatis.mapping.SqlSource getBoundSql '{params,returnObj,throwExp}'拿出來的sql的確沒有LIMIT
- TODO 待修改

打開sql日志,也有觀察到Limit語句,雖然只出現(xiàn)了一次,但說明是有分頁的。
因此:猜測1不成立。
2.查看PaginationInterceptor
PaginationInterceptor攔截的是StatementHandler#prepare,在prepare方法執(zhí)行的時(shí)候,才會添加Limit語句。而在調(diào)用添加Limit語句之前,已經(jīng)拿了緩存中的數(shù)據(jù),不會真正執(zhí)行查詢了。
- 調(diào)用順序?yàn)椋?/li>

由上圖可以看出,當(dāng)生成cacheKey的時(shí)候,由于此時(shí)的boundSql還未被攔截,不含有Limit語句,每次boundSql都一樣,導(dǎo)致cacheKey都一樣。
因此:
- 只有第一次查詢會走不到緩存,然后被prepare階段被攔截,添加Limit語句后查詢數(shù)據(jù)庫。將數(shù)據(jù)放入一級緩存中。
- 第二次查詢開始,由于有事務(wù),會嘗試?yán)靡患壘彺嬷械臄?shù)據(jù),且cacheKey一樣,緩存有對應(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)先查一級緩存,無緩存則查數(shù)據(jù)庫
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語句
/**
* <p>
* MYSQL 數(shù)據(jù)庫分頁語句組裝實(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)致雖然傳了不同的頁數(shù),但是提前生成的cacheKey一樣。
- 使用了事務(wù),導(dǎo)致用到同一sqlSession,走到了一級緩存。
解決方法
- 升級Mybatis-Plus,高版本的MybatisPlusInterceptor來分頁。(MybatisPlusInterceptor的攔截在cacheKey生成之前,可以避免上述問題)
- 不在事務(wù)中使用分頁查詢。
總結(jié)
分頁太深,查詢太慢,最后還是放棄了這種分頁查詢方式。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- Mybatis-Plus中分頁插件PaginationInterceptor的使用
- 解決mybatis-plus3.4.1分頁插件PaginationInterceptor和防止全表更新與刪除插件SqlExplainInterceptor過時(shí)失效問題
- Mybatis-plus新版本分頁失效PaginationInterceptor過時(shí)的問題
- Spring Boot集成MyBatis-Plus 自定義攔截器實(shí)現(xiàn)動態(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啟動的實(shí)現(xiàn)
這篇文章主要介紹了idea2020.1設(shè)置多個(gè)spring boot的service啟動,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
淺析java中ArrayList與Vector的區(qū)別以及HashMap與Hashtable的區(qū)別
以下是對java中ArrayList與Vector的區(qū)別以及HashMap與Hashtable的區(qū)別進(jìn)行了詳細(xì)的解析。需要的朋友可以過來參考下2013-08-08
詳解Mybatis極其(最)簡(好)單(用)的一個(gè)分頁插件
這篇文章主要介紹了詳解Mybatis極其(最)簡(好)單(用)的一個(gè)分頁插件,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2016-12-12
SpringBoot集成nacos動態(tài)刷新數(shù)據(jù)源的實(shí)現(xiàn)示例
這篇文章主要介紹了SpringBoot集成nacos動態(tài)刷新數(shù)據(jù)源的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
如何通過properties文件配置web.xml中的參數(shù)
這篇文章主要介紹了如何通過properties文件配置web.xml中的參數(shù)方法,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Java基于MySQL實(shí)現(xiàn)學(xué)生管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java基于MySQL實(shí)現(xiàn)學(xué)生管理系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
SpringCloud+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

