分页合理化是什么?
一、前言
大家好!我是sum墨,一個一線的底層碼農,平時喜歡研究和思考一些技術相關的問題并整理成文,限于本人水平,如果文章和代碼有表述不當之處,還請不吝賜教。
只要是干過后臺系統的同學應該都做過分頁查詢吧,前端發送帶有頁碼(pageNum)和每頁顯示數量(pageSize)的請求,后端根據這些參數來提取并返回相應的數據集。在SpringBoot框架中,經常會使用Mybatis+PageHelper的方式實現這個功能。
但大家可能對分頁合理化這個詞有點兒陌生,不過應該都遇到過因為它產生的問題。這些問題不會觸發明顯的錯誤,所以大家一般都忽視了這個問題。那么啥是分頁合理化,我來舉幾個例子:
它的定義:分頁合理化通常是指后端在處理分頁請求時會自動校正不合理的分頁參數,以確保用戶始終收到有效的數據響應。
1. 請求頁碼超出范圍:
假設數據庫中有100條記錄,每頁展示10條,那么就應該只有10頁數據。如果用戶請求第11頁,不合理化處理可能會返回一個空的數據集,告訴用戶沒有更多數據。開啟分頁合理化后,系統可能會返回第10頁的數據(即最后一頁的數據),而不是一個空集。
2. 請求頁碼小于1:
用戶請求的頁碼如果是0或負數,這在分頁上下文中是沒有意義的。開啟分頁合理化后,系統會將這種請求的頁碼調整為1,返回第一頁的數據。
3. 請求的數據大小小于1:
如果用戶請求的數據大小為0或負數,這也是無效的,因為它意味著用戶不希望獲取任何數據。開啟分頁合理化后,系統可能會設置一個默認的頁面大小,比如每頁顯示10條數據。
4. 請求的數據大小不合理:
如果用戶請求的數據大小非常大,比如一次請求1000條數據,這可能會給服務器帶來不必要的壓力。開啟分頁合理化后,系統可能會限制頁面大小的上限,比如最多只允許每頁顯示100條數據。
二、為啥要設置分頁合理化?
其實上面那些問題對于后端來講很合理,頁碼和頁大小設置不正確查詢不出來值難道不合理嗎?唯一的問題就是如果一次性查詢太多條數據服務器壓力確實大,但如果是產品要求的那也沒辦法呀!
真正讓我不得不解決這個問題的原因是前端的一個BUG,這個BUG是啥樣的呢?我來給大家描述一下。
1. BUG復現
我們先看看前端的分頁組件
前端的這個分頁組件大家應該很常見,它需要兩個參數:總行數、每頁行數。比如說現在總條數是6條,每頁展示5條,那么會有2頁,沒啥問題對吧。
那么,現在我問一個問題:我們切換到第二頁,把第二頁僅剩的一條數據給刪除掉,會出現什么情況?
理想情況:頁碼自動切換到第1頁,并查詢第一頁的數據;
真實情況:頁碼切換到了第1頁,但是查詢不到數據,這明顯就是一個BUG!
2. BUG分析
1. 用戶切換到第二頁,前端發起了請求,如:http://localhost:8080/user/pageQuery?pageNum=2&pageSize=5 ,此時第2頁有一條數據;
2. 用戶刪除第2頁的唯一數據后,前端發起查詢請求,但還是第2頁的查詢,因為總數據的變化前端只能通過下一次的查詢才能知道,但此時數據查詢為空;
3. 雖然第二次查詢的數據集為空,但是總條數已經變化了,只剩下5條,前端分頁組件根據計算得出只剩下一頁,所以自動切換到第1頁;
可以看出這個BUG是分頁查詢的一個臨界狀態產生的,必現、中低頻,屬于必須修復的那一類。不過這個BUG想甩給前端,估計不行,因為總條數的變化只有后端知道,必須得后端修了。
三、設置分頁合理化
咋一聽這個BUG有點兒復雜,但如果你使用的是PageHelper框架,那么修復它非常簡單,只需要兩行配置。
在application.yml或application.properties中添加
pagehelper.helper-dialect=mysql
pagehelper.reasonable=true
只要加了這兩行配置,這個BUG就能解決。因為配置是全局的,如果你只想對單個查詢場景生效,那就在設置分頁參數的時候,加一個參數,如下:
PageHelper.startPage(pageNumber, pageSize, true);
四、分頁合理化配置的原理說明
這個BUG如果要自己解決的話,是不是感覺有點頭痛了,但是人家PageHelper早就想到這個問題了,就像游戲開掛一樣,一個配置就解決了這個麻煩的問題。
用的時候確實很爽,但是我卻有點擔心,這個配置現在解決了這個BUG,會不會導致新的BUG呢?如果真的出現了新BUG,我應該怎么做呢?所以我決定研究一下它的基礎原理。
在com.github.pagehelper.Page類下,找到了這段核心源碼,這段應該就是分頁合理化的實現邏輯
// 省略其他代碼
public Page<E> setReasonable(Boolean reasonable) {
if (reasonable == null) {
return this;
}
this.reasonable = reasonable;
//分頁合理化,針對不合理的頁碼自動處理
if (this.reasonable && this.pageNum <= 0) {
this.pageNum = 1;
calculateStartAndEndRow();
}
return this;
}
// 省略其他代碼
// 省略其他代碼
/**
* 計算起止行號
*/
private void calculateStartAndEndRow() {
this.startRow = this.pageNum > 0 ? (this.pageNum - 1) * this.pageSize : 0;
this.endRow = this.startRow + this.pageSize * (this.pageNum > 0 ? 1 : 0);
}
// 省略其他代碼
還有一些代碼我沒貼,比如PageInterceptor#intercept方法,這里我整理了一下它的執行流程圖,如下:
看了圖解,這套配置還挺清晰的,懂了怎么回事兒,用起來也就放心了。記得剛開始寫代碼時,啥都希望有人給弄好了,最好是拿來即用。但時間一長,自己修過一堆BUG,才發現只有自己弄明白的代碼才靠譜,什么都想親手來。等真正搞懂了一些底層的東西,才意識到要想造出好東西,得先學會站在巨人的肩膀上。學習嘛,沒個頭兒!
總結
- 上一篇: windows使用rclone挂载ali
- 下一篇: C++ Qt开发:Charts绘制各类图