某天我正在工位上聽着 Vicotry,愉快地敲着 hello world ,這感覺就像我寫的代碼能征服世界。突然運維給我打了一個電話,說我們某台服務器 OOM 了,要我過去看下,這感覺就像 xxx,你懂的。
去運維室、登錄服務器、查看日志、....一頓操作猛如虎,看到一個 List 對象 600MB +(原諒我們服務器 low,運維比較小氣,就給 1C2G 的服務器),檢查當時的 SQL 語句,一看,我的乖乖,將近 4000w + 條數據。我的第一感覺就是,難道又是哪個業務在導出大批量數據?但是我們所有的 Excel 導出數據都做了校驗,數據量大於 5w 條就后台分批次導出(所以有時候你要慶幸服務器 low,因為服務器 low,你就需要進行各種各樣的優化,所有大數據量的操作都需要想辦法優化,所以我們這個應用就有了各種有意思的騷操作,后面有機會分享下)。難道沒有控制住?看了日志並沒有發現大數據量的 Excel 導出,所以可以斷定是分頁的地方沒有分頁。看代碼在一個 if
語句里面找到了坑,如下:
PageHelper.startPage(queryDTO.getPage(), queryDTO.getLimit());
Page<UserDTO> page;
if (isWitchFlag()) {
page = userMapper.selectUserList(queryDTO);
}
isWitchFlag()
:
private boolean isWitchFlag() {
String witchFlag = systemConfigMapper.selectSwitchFlag("key");
return "1".equals(witchFlag);
}
對 PageHelper 不是很熟悉的人一定不知道這個坑在哪里!在 PageHelper 使用文檔(https://pagehelper.github.io/faq/)中第一句就闡述了:
只有緊跟在 PageHelper.startPage 方法后的第一個 Mybatis 的查詢(Select)方法會被分頁。。請注意關鍵詞緊跟。為什么要緊跟呢?因為 PageHelper 的分頁原理使用了 ThreadLocal,他的分頁參數和線程是綁定在一起的,當我們執行 PageHelper.startPage()
語句時,他會將分頁參數綁定到 ThreadLocal 中:
setLocalPage()
:
在攔截器 PageInterceptor
中,最后的 finally 會將 Page 分頁信息給 remove 掉:
所以,上面那段代碼的分頁信息被 if 語句中的 select 查詢語句給消耗掉了,下面真正需要分頁的語句當然就不會執行分頁信息啦。怎么解決?兩種方案:
- 治標不治本:將
PageHelper.startPage()
挪到 if 語句里面,讓真正的查詢語句緊挨着它。這種方案不治本的原因在於,如果又有小伙伴不知道這個坑,有可能又會踩。 - 治標治本:使用 Function Lamdba 表達式。
使用 Function Lamdba 來將 PageHelper.startPage()
與分頁查詢語句緊挨在一起,規避掉這個坑
首先我們需要定義一個 PageHelperTool,該 PageHelperTool 是封裝了分頁語句:
@Builder
public class PageHelperTool<P,R> {
private final Function<P, Page<R>> pageFunction;
public Page<R> getPageInfo(P request) {
PageHelper.startPage(((PageRequest)request).getPage(),((PageRequest)request).getLimit());
return pageFunction.apply(request);
}
}
然后將分頁的地方全部替換為 PageHelperTool 就可以了:
Page<UserDTO> page;
if (isWitchFlag()) {
PageHelperTool<QueryDTO,UserDTO> pageHelperTool = PageHelperTool.<QueryDTO,UserDTO>builder()
.pageFunction(userMapper::selectUserList)
.build();
page = pageHelperTool.getPageInfo(queryDTO);
}
這樣就可以徹底解決因誤用 PageHelper 導致分頁失效的問題了。
最后一句話:注意看文檔啊!!!!!!