PageHelper中分頁失效的原因分析與正確方法實(shí)踐
1. 問題現(xiàn)象
在使用 PageHelper 插件開發(fā)查詢接口時(shí),出現(xiàn)分頁失效的情況:接口返回了全部數(shù)據(jù)而非當(dāng)前頁數(shù)據(jù),且 PageInfo 對(duì)象中的分頁元數(shù)據(jù)(如 total 總條數(shù)、pages 總頁數(shù))計(jì)算錯(cuò)誤。
錯(cuò)誤代碼示例
public BaseResponse<MyDataDTO> queryDataList(int pageNum, int pageSize, Map<String, Object> params) {
BaseResponse response = new BaseResponse();
// 1. 錯(cuò)誤:在設(shè)置分頁參數(shù)前執(zhí)行了數(shù)據(jù)庫查詢
List<MyDataDTO> dataList = myMapper.selectData(params);
// 2. 判空邏輯
if (!CollectionUtils.isEmpty(dataList)) {
// 3. 錯(cuò)誤:查詢?cè)缫淹瓿?,此時(shí)調(diào)用 startPage 無效
PageHelper.startPage(pageNum, pageSize);
// 4. 錯(cuò)誤:直接使用全量 List 包裝 PageInfo,無法獲取數(shù)據(jù)庫真實(shí)總數(shù)
PageInfo<MyDataDTO> pageInfo = new PageInfo<>(dataList);
response.setResult(wrapPageResult(pageInfo));
return response;
}
response.setResult(Collections.EMPTY_LIST);
return response;
}
2. 原理分析
PageHelper 的核心機(jī)制基于 ThreadLocal 和 MyBatis 攔截器(Interceptor)。
2.1 執(zhí)行流程
PageHelper 不是在內(nèi)存中對(duì)結(jié)果集進(jìn)行截取,而是通過攔截器修改 SQL 語句。
設(shè)置參數(shù):調(diào)用 PageHelper.startPage(...) 時(shí),插件會(huì)將分頁參數(shù)(pageNum, pageSize)存入當(dāng)前線程的 ThreadLocal 中。
攔截 SQL:當(dāng) MyBatis 執(zhí)行 Mapper 方法時(shí),PageHelper 攔截器會(huì)觸發(fā)。
SQL 改寫:
- 攔截器檢查
ThreadLocal中是否存在分頁參數(shù)。 - 若存在:攔截器會(huì)根據(jù)數(shù)據(jù)庫方言(如 MySQL)生成
SELECT COUNT(0)語句獲取總數(shù),并將原 SQL 改寫為帶LIMIT/OFFSET的分頁 SQL 執(zhí)行。 - 若不存在:攔截器直接放行,執(zhí)行原始 SQL。
清理上下文:SQL 執(zhí)行結(jié)束后,攔截器會(huì)清除 ThreadLocal 中的分頁參數(shù),避免污染后續(xù)查詢。
2.2 失效原因
在上述錯(cuò)誤代碼中:
- 執(zhí)行順序錯(cuò)誤:
myMapper.selectData在PageHelper.startPage之前執(zhí)行。 - 攔截失敗:執(zhí)行查詢時(shí),
ThreadLocal中沒有任何分頁參數(shù),攔截器未生效,MyBatis 執(zhí)行了全量查詢。 - 參數(shù)無效:查詢結(jié)束后才調(diào)用
startPage,雖然設(shè)置了ThreadLocal,但 SQL 交互已結(jié)束,該參數(shù)未被消費(fèi)。 - 元數(shù)據(jù)錯(cuò)誤:
PageInfo接收的是全量 List,因此它只能基于 List 的大小計(jì)算total,導(dǎo)致分頁信息不符合預(yù)期。
3. 正確實(shí)現(xiàn)
核心原則:PageHelper.startPage 必須緊鄰 Mapper 查詢方法之前調(diào)用。
修正代碼
public BaseResponse<MyDataDTO> queryDataList(int pageNum, int pageSize, Map<String, Object> params) {
BaseResponse response = new BaseResponse();
// 1. 設(shè)置分頁參數(shù)(存入 ThreadLocal)
PageHelper.startPage(pageNum, pageSize);
// 2. 執(zhí)行查詢(攔截器生效,自動(dòng)改寫 SQL 并執(zhí)行 Count 查詢)
// 注意:此時(shí)返回的 list 實(shí)際類型為 Page<E>
List<MyDataDTO> dataList = myMapper.selectData(params);
// 3. 獲取分頁結(jié)果
PageInfo<MyDataDTO> pageInfo = new PageInfo<>(dataList);
// 4. 結(jié)果處理(PageInfo 可安全處理空集合)
if (!CollectionUtils.isEmpty(dataList)) {
// pageInfo.getTotal() 為數(shù)據(jù)庫真實(shí)總數(shù)
response.setResult(wrapPageResult(pageInfo));
} else {
response.setResult(Collections.EMPTY_LIST);
}
return response;
}
4. 最佳實(shí)踐與注意事項(xiàng)
嚴(yán)格遵守調(diào)用順序 必須保證 startPage -> Mapper查詢 -> PageInfo包裝 的執(zhí)行順序。
避免邏輯穿插 嚴(yán)禁在 startPage 和 Mapper查詢 之間插入其他 SQL 操作或復(fù)雜邏輯。
風(fēng)險(xiǎn):PageHelper 的分頁參數(shù)是“一次性消費(fèi)”的。如果在分頁查詢前插入了其他 SQL(如查詢用戶信息),分頁參數(shù)會(huì)被那條 SQL 消費(fèi)掉,導(dǎo)致原本需要分頁的主查詢失效。
PageInfo 的健壯性 無需為了判空調(diào)整代碼順序。PageInfo 對(duì)空 List 有良好的兼容性,若查詢結(jié)果為空,它會(huì)自動(dòng)設(shè)置 total=0,不會(huì)拋出異常。
大數(shù)據(jù)量風(fēng)險(xiǎn) 如果因順序錯(cuò)誤導(dǎo)致分頁失效,全量查詢可能會(huì)將百萬級(jí)數(shù)據(jù)加載至內(nèi)存,極易引發(fā) OOM(內(nèi)存溢出),影響系統(tǒng)穩(wěn)定性。
到此這篇關(guān)于PageHelper中分頁失效的原因分析與正確方法實(shí)踐的文章就介紹到這了,更多相關(guān)PageHelper分頁失效解決內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java開發(fā)之Spring連接數(shù)據(jù)庫方法實(shí)例分析
這篇文章主要介紹了Java開發(fā)之Spring連接數(shù)據(jù)庫方法,以實(shí)例形式較為詳細(xì)的分析了Java Spring開發(fā)中針對(duì)數(shù)據(jù)庫的相關(guān)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
淺談Java動(dòng)態(tài)代理的實(shí)現(xiàn)
最近,小組同事做代碼改造時(shí),使用到了動(dòng)態(tài)代理,自己閱讀時(shí),發(fā)現(xiàn)對(duì)代理這種設(shè)計(jì)模式都不怎么清楚,導(dǎo)致理解代碼也很困難 自己唯一能看懂的,大概就是handler中的invoke方法 ,文中作出了非常詳細(xì)的介紹,需要的朋友可以參考下2021-05-05
SpringBoot3整合Hutool-captcha實(shí)現(xiàn)圖形驗(yàn)證碼
在整合技術(shù)框架的時(shí)候,想找一個(gè)圖形驗(yàn)證碼相關(guān)的框架,看到很多驗(yàn)證碼的maven庫不再更新了或中央倉庫下載不下來,還需要多引入依賴,后面看到了Hutool圖形驗(yàn)證碼(Hutool-captcha)中對(duì)驗(yàn)證碼的實(shí)現(xiàn),所以本文介紹了SpringBoot3整合Hutool-captcha實(shí)現(xiàn)圖形驗(yàn)證碼2024-11-11
Java多維數(shù)組和Arrays類方法總結(jié)詳解
這篇文章主要介紹了Java多維數(shù)組和Arrays類方法總結(jié)詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
詳解Java的Proxy動(dòng)態(tài)代理機(jī)制
Java有兩種代理方式,一種是靜態(tài)代理,另一種是動(dòng)態(tài)代理。對(duì)于靜態(tài)代理,其實(shí)就是通過依賴注入,對(duì)對(duì)象進(jìn)行封裝,不讓外部知道實(shí)現(xiàn)的細(xì)節(jié)。很多 API 就是通過這種形式來封裝的2021-06-06
Java中通過Class類獲取Class對(duì)象的方法詳解
這篇文章主要給大家介紹了關(guān)于Java中通過Class類獲取Class對(duì)象的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-08-08

