目錄
●爲什麼要做分頁查詢?
大家登陸網站,使用到查詢功能的時候有沒有發現,其實頁面上幾乎都不會給你展示所有內容,而是以分頁的方式進行展示,我們來看看幾個常見的場景:
CSDN博客——
站長素材——
Printrest——
包括大家常用的淘寶、知乎、微博、視頻網站等,無一例外都是採用了分頁查詢的機制,具體表現在:
1、查詢的數據量相對較多,每次給用戶展示一部分;
2、用戶通過上一頁、下一頁、頁碼跳轉、滾動條(瀑布流網站)等方式獲取其餘的數據。
這麼做的原因有以下幾點:
1、當數據量很大的時候,後臺全部查詢出來是一個很耗時間的操作;
2、退一步說,就算了用到分佈式、緩存等技術,降低後臺操作時間,但大量數據在網絡上傳輸給用戶時也是很耗時的,例如目前常見的網絡環境也不過就是10M或者100M;
3、再退一步說,就算查詢和傳輸都是迅速完成的,把這麼多數據,全部展現在用戶面前,讓用戶自己去大海撈針般的查看,體驗也是很糟糕的。
因此,無論是從性能表現上還是用戶體驗上,分頁查詢是必須要做的。
●如何實現分頁查詢
按筆者的理解,分頁查詢可以分爲兩類,一類稱之爲“真分頁”;一類稱之爲“假分頁”。
“真分頁”是在後臺按需查詢所要顯示的數據,回傳給前臺展示;“假分頁”是後臺查詢出所有數據,回傳給前臺,由前臺來進行分頁展示。毫無疑問,“假分頁”是一種自欺欺人的做法。
“真分頁”根據實現的技術,筆者也將其分爲兩類,一類利用同步阻塞,一類利用異步通知。前者等待數據到達頁面之後用戶纔可以進行其他操作,後者這是利用Ajax,不對用戶的操作產生阻塞,表現在用戶可以點擊其他按鈕/菜單,進行別的操作。正常來說,都是選擇異步通知的方式進行真分頁。
流程上來說,用戶設置好查詢條件(例如輸入查詢起止時間、查詢的類別等),點擊查詢按鈕(當然,不同的網站表現也不同,例如有的是加載網頁後直接查詢出一些分頁內容,例如點擊淘寶的已購買的寶貝,就會自動按時間排序查出最新的X條數據,之後用戶可以設置查詢條件再點查詢),頁面向服務器後臺發起查詢請求,服務器根據查詢條件,拼接好查詢SQL語句並執行,查出滿足條件的前XX條數據,並且記錄下總的記錄條數,通過分頁對象返回給前臺。前臺翻頁的時候會把當前頁數、每頁展示數據量等信息告訴服務器後臺,繼而查詢之後的數據。
整個流程的時序圖如下:
值得注意的是,用戶進行首次查詢的時候,網頁其實是發起了兩次請求,將查詢數據(設置了顯示數量、類別等條件)和查詢總記錄的條數分開請求,如果不分開,count(*)的操作可能會消耗大量時間,造成用戶遲遲無法看到返回的數據。因此,先把數據展示給用戶,改善用戶的體驗。而在之後的翻頁操作中,就不用再去查詢記錄的總條數了,因爲第一次已經查詢過,並且給到了前臺計算總頁數並保存,之後的翻頁操作,前臺除了將查詢的限制條件發給後臺外,再講查詢的起始值也發過去就可以了。例如每頁顯示20條數據,用戶翻頁到第五頁,那麼起始值就應該是20*(5-1)=80。第一次點擊查詢的時候頁面傳過去的起始值是0。
●分頁對象的設計
通常分頁對象需要設計包括以下成員變量:頁面大小(即一頁展示多少條數據)、數據起始id、總的記錄條數、最後一條數據的id、數據(一般是一個List對象)、
通常分頁對象還需要設計包括以下方法:相應的get/set方法、取總頁數的方法(當然也可以把這個邏輯下放到前臺去執行)、取當前頁碼的方法(同前)、是否有上下頁以及一些對應的數據所在位置的計算函數。
來看一下具體的代碼實現:
import java.util.ArrayList;
public class Page {
// 常量,定義默認的頁面大小,即一頁默認展示多少數據
private static int DEFAULT_PAGE_SIZE = 10;
// 每頁的記錄數,先設爲默認值
private int pageSize = DEFAULT_PAGE_SIZE;
// 當前頁第一條數據在List中的位置,從0開始
private long start;
// 當前頁中存放的記錄,類型一般爲List<T>
private Object data;
// 總記錄數
private long totalCount;
// 最大的一條記錄的id
private String recordMaxIds;
/**
* 構造方法,構造空頁.
*/
public Page() {
this(0, 0, DEFAULT_PAGE_SIZE, new ArrayList());
}
/**
* 默認構造方法.
* @param start 本頁數據在數據庫中的起始位置
* @param totalSize 數據庫中總記錄條數
* @param pageSize 本頁容量
* @param data 本頁裏面的數據
*/
public Page(long start, long totalSize, int pageSize, Object data) {
if(pageSize == 0 ){
pageSize = DEFAULT_PAGE_SIZE;
}else{
this.pageSize = pageSize;
}
this.start = start;
this.totalCount = totalSize;
this.data = data;
}
/**
* 取總記錄數.
*/
public long getTotalCount() {
return this.totalCount;
}
/**
* 取總頁數.
*/
public long getTotalPageCount() {
if(totalCount == 0){
return 1;
}else{
if (totalCount % pageSize == 0)
return totalCount / pageSize;
else
return totalCount / pageSize + 1;
}
}
/**
* 取每頁數據容量.
*/
public int getPageSize() {
return pageSize;
}
/**
* 取當前頁中的記錄.
*/
public Object getResult() {
return data;
}
/**
* 取該頁當前頁碼,頁碼從1開始.
*/
public long getCurrentPageNo() {
if(pageSize != 0){
return start / pageSize + 1;
}else{
return 1;
}
}
/**
* 該頁是否有下一頁.
*/
public boolean hasNextPage() {
return this.getCurrentPageNo() < this.getTotalPageCount() - 1;
}
/**
* 該頁是否有上一頁.
*/
public boolean hasPreviousPage() {
return this.getCurrentPageNo() > 1;
}
/**
* 獲取任一頁第一條數據在數據集的位置.
* @param pageNo 從1開始的頁號
* @param pageSize 每頁記錄條數
* @return 該頁第一條數據
*/
public static int getStartOfPage(int pageNo, int pageSize) {
return (pageNo - 1) * pageSize;
}
/**
* 獲取頁號,從1開始.
* @param startIndex 開始索引
* @param pageSize 每頁記錄條數
* @return 從1開始的頁號
*/
public static int getPageNo(int startIndex, int pageSize) {
return startIndex % pageSize == 0 ? startIndex / pageSize : startIndex / pageSize + 1;
}
public String getRecordMaxIds() {
return recordMaxIds;
}
public void setRecordMaxIds(String recordMaxIds) {
this.recordMaxIds = recordMaxIds;
}
public void setData(Object data) {
this.data = data;
}
}
●其他補充的信息
最後,我們要知道,分頁查詢最終還是要落實到數據庫去執行,通過分頁查詢的SQL是進行查詢時避免全表掃描。因此,分頁這個業務,在執行數據庫操作時,需要構造不同的查詢語句,通常來說MySql利用的是limit子句,PostgreSQL利用的是limit和offset子句來實現的,可以人爲去Dao層寫對應的函數。如果使用了Hibernate等框架,還可以直接使用它所提供的函數去進行數據庫分頁,例如Hibernate中的setFirstResult()和setMaxResults()函數來控制查詢與返回結果集的條數。而這個控制數量、偏移量的值則是第一次查詢出總記錄,加上頁面設置的每頁數據量多少來共同計算的。
分頁對象回傳給前端網頁的時候,一般可以採用JQuery的Ajax技術進行接收處理,筆者目前從事後端開發工作,這一塊暫時就先不和大家分享了。今天,你學會了嗎?