從分頁查詢談用戶體驗與性能表現

目錄

 

●爲什麼要做分頁查詢?

●如何實現分頁查詢

●分頁對象的設計

●其他補充的信息


●爲什麼要做分頁查詢?

大家登陸網站,使用到查詢功能的時候有沒有發現,其實頁面上幾乎都不會給你展示所有內容,而是以分頁的方式進行展示,我們來看看幾個常見的場景:

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技術進行接收處理,筆者目前從事後端開發工作,這一塊暫時就先不和大家分享了。今天,你學會了嗎?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章