1. 程式人生 > >JavaWeb分頁查詢思路剖析

JavaWeb分頁查詢思路剖析

案例分析:

 

    前端-->後臺: 


        1. 當前端需要對某項資料進行條件查詢時, 需要給出查詢條件, 故得出第一個引數: "查詢條件";
        2. 當查詢結果過多,無法全部顯示, 或全部顯示頁面不夠清晰友好時, 需要對查詢結果進行分頁顯示, 故得出第二個引數: "目標頁頁數":
            目標頁頁數: 初始頁數為1, 使用者選擇響應按鈕時, 即確認出新的"目標頁頁數""; 
        3. 當需要按某種順序檢視查詢結果時, 需要將查詢結果按條件排序,故得出第三個引數: "排序條件"

;


    後臺-->前端:


        1. 當前端需要向客戶展示資訊時, 需要拿到顯示的內容,故得第一個引數: "當前頁查詢結果集合";
        2. 同時我們需要在前端顯示分頁條, 以京東分頁條為例分析:
           
            a. 上一頁/ 下一頁/ 其它目標頁頁數, 都可由當前頁數或直接選擇得出, 故得出第二個引數: "當前頁數"

;
            b. 總頁數需要由: 查詢結果總數 和 每頁顯示結果數 得出, 故得出第三個引數: "查詢結果總數", 第四個引數: "每頁顯示條數";
            c. 當總頁數大於一定值時, 顯示出所有頁數的對應選擇按鈕, 影響展示效果和使用者體驗, 故僅顯示某些頁面按鈕即可:
                如:
                    展示 首頁/ 尾頁 按鈕;
                    展示 上一頁/ 下一頁 按鈕;
                    展示 當前頁前幾頁/ 當前頁後幾頁 按鈕;
                    展示 頁面跳轉的輸入框/ 確認跳轉按鈕;

                因而得出第五個引數: "之前可選頁數", 第六個引數: "之後可選頁數";


    根據分析得出:


        前端-->後臺 引數:
            1. 查詢條件;
            2. 目標頁頁數;
            3. 排序條件;
        後臺-->前端 引數:
            1. 當前頁查詢結果集合;
            2. 當前頁數;
            3. 查詢結果總數; (所有查詢條件的結果總數)
            4. 每頁顯示條數;
            5. 之前可選頁數;
            6. 之後可選頁數;


程式設計思想:


    後臺業務邏輯思路:


        1.每頁顯示條數:一般根據使用者體驗/ 排版要求等確定, 故直接在後臺給出(或配置檔案匯入);
        2.目標頁查詢結果集合: 根據查詢條件/ 當前頁數/ 排序條件 及每頁顯示條數, 可以得到sql語句的 "虛擬碼" :
            "select * from 資料庫表 where 查詢條件 sort by 排序條件 limit 起始位置,每頁顯示條數;" ;
        3. 當前頁數: 即前臺傳遞給後臺的目標頁數, 因前臺頁面不直接通訊, 故回傳給前臺即可;
        4. 查詢結果總數: 根據:查詢條件,可得到sql語句的 "虛擬碼" :
            "select count(*) from 資料庫表 where 查詢條件;" ;
        5. 之前可選頁數: 根據總頁數/ 當前頁數/ "前m後n"展示效果的 m/ n數值得出;
        6. 之後可選頁數: 同上(5);


    整體架構:


        Web分頁查詢功能實現涉及:前端頁面/ 後臺業務邏輯處理/ 資料庫查詢/ 查詢結果回傳等, 故基於MVC模式思想 並採用"經典三層體系架構":表示層/ 業務邏輯層/ 資料訪問層;


    基本思維:


        1. 面向物件: 
            a. 單個查詢結果是一個整體資料集, 故封裝為實體類物件, 如商品物件/ 聯絡人資訊物件/ 學生資訊物件等; 
            b. 後端傳遞給前端的所有涉及分頁查詢資料, 可抽象為一個具有實體類性質的工具類, 並加入簡單業務邏輯方法: 如計算:總頁數/ 排序起始位置數 等;
        2. 程式碼複用:
            a. 程式碼複用的第一個體現就是 三層體系架構, 不再贅述;
            b. 工具類的適當使用:
                i. 資料庫相關程式碼, 如: 註冊/ 配置/ 流的關閉/連線池使用等, 封裝成工具類, 使用時僅建立物件(靜態方法可省略此步), 編寫sql語句和提交引數即可;
                ii. 複用性較高/ 程式碼量較大的其它業務邏輯, 封裝成工具類, 使用時傳入引數得到結果集即可, 如本例中: "根據總頁數/ 當前頁數/ "前m後n"展示效果的 m/ n數值得出:之前和之後可選頁數";
                    注意: 這裡的複用性高一定程度指的是職業生涯和公司所有專案中的複用, 而不僅是指當前專案;
 

部分程式碼展示:

前端頁面中: jstl/el展示分頁條程式碼塊:

<c:if test="${pb.totalPage>1}"> <%--若滿足頁數大於1頁--%>
            <nav aria-label="Page navigation" class="text-center"><%--BootStrap框架的分頁條--%>
                <ul class="pagination">
                    <li><a href="managecontact?code=manage&curPage=1" onclick="serch(this)">首頁</a></li><%--首頁按鈕--%>
                    <c:if test="${pb.curPage<=1}"><%--若當前頁是首頁,則上一頁按鈕不可用--%>
                        <li class="disabled">
                            <a href="javaScript:void(0)" aria-label="Previous" onclick="serch(this)">
                                <span aria-hidden="true">&laquo;</span>
                            </a>
                        </li>
                    </c:if>
                    <c:if test="${pb.curPage>1}"><%--若當前頁不是首頁,則可點選上一頁--%>
                        <li>
                            <a href="managecontact?code=manage&curPage=${pb.curPage-1}" aria-label="Previous" onclick="serch(this)">
                                <span aria-hidden="true">&laquo;</span>
                            </a>
                        </li>
                    </c:if>

                    <c:forEach begin="${pb.start}" end="${pb.end}" step="1" var="cur"><%--迴圈給出當前頁及"前m後n"的頁數按鈕--%>
                        <c:if test="${cur==pb.curPage}">
                            <li class="active"><a href="javaScript:void(0)" onclick="serch(this)">${cur}</a></li>
                        </c:if>
                        <c:if test="${cur!=pb.curPage}">
                            <li ><a href="managecontact?code=manage&curPage=${cur}" onclick="serch(this)">${cur}</a></li>
                        </c:if>
                    </c:forEach>

                    <c:if test="${pb.curPage==pb.totalPage}"><%--若當前頁是尾頁,則下一頁按鈕不可用--%>
                        <li class="disabled" >
                            <a href="javaScript:void(0)" aria-label="Next" onclick="serch(this)">
                                <span aria-hidden="true">&raquo;</span>
                            </a>
                        </li>
                    </c:if>
                    <c:if test="${pb.curPage<pb.totalPage}"><%--若當前頁不是尾頁,則下一頁按鈕可用--%>
                        <li>
                            <a href="managecontact?code=manage&curPage=${pb.curPage+1}" aria-label="Next" onclick="serch(this)">
                                <span aria-hidden="true">&raquo;</span>
                            </a>
                        </li>
                    </c:if>
                    <li><a href="managecontact?code=manage&curPage=${pb.totalPage}" onclick="serch(this)">尾頁</a></li><%--尾頁按鈕--%>
                </ul>
            </nav>
        </c:if>

前端頁面所需分頁展示相關資料裝入實體工具類傳入, 故實體工具類程式碼如下:

package com.wen.utils;

import java.util.List;
import java.util.Objects;

public class PageBean <T>{
    /*
    查詢到的資料List<Contact> data
    總條數:int count
    每頁顯示條數:int pageSize
    總頁數:int totalPage
    當前頁:int curPage
    開始頁數:int start
    結束頁數:int end
     */
    private List<T> data;//顯示資料
    private int count;//總條數
    private int pageSize;//每頁條數
    private int totalPage;//總頁數
    private int curPage;//當前頁
    private int start;//開始顯示頁數
    private int end;//最後顯示頁數

    public int getTotalPage() {
        return (int)Math.ceil(count*1.0/pageSize);//總頁數=總條數/每頁展示數  向上取整
    }

    public PageBean(int pageSize, int curPage) {
        this.pageSize = pageSize;
        this.curPage = curPage;
    }

    public int getStartIndex(){//返回分頁開始位置索引
        return pageSize * (curPage - 1);
    }

    private int[] ints = PageUtils.showPage(curPage, this.getTotalPage());//呼叫工具類計算start和end

    public int getStart() {
        int[] ints = PageUtils.showPage(curPage, this.getTotalPage());//呼叫工具類計算start和end
        return ints[0];//設定start
    }

    public int getEnd() {
        int[] ints = PageUtils.showPage(curPage, this.getTotalPage());//呼叫工具類計算start和end
        return ints[1];//設定end
    }

    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        this.data = data;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public int getCurPage() {
        return curPage;
    }

    public void setCurPage(int curPage) {
        this.curPage = curPage;
    }

    @Override
    public String toString() {
        return "PageBean{" +
                "data=" + data +
                ", count=" + count +
                ", pageSize=" + pageSize +
                ", totalPage=" + totalPage +
                ", curPage=" + curPage +
                ", start=" + start +
                ", end=" + end +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PageBean<?> pageBean = (PageBean<?>) o;
        return count == pageBean.count &&
                pageSize == pageBean.pageSize &&
                totalPage == pageBean.totalPage &&
                curPage == pageBean.curPage &&
                start == pageBean.start &&
                end == pageBean.end &&
                Objects.equals(data, pageBean.data);
    }

    @Override
    public int hashCode() {

        return Objects.hash(data, count, pageSize, totalPage, curPage, start, end);
    }
}

上述PageBean中用到的PageUtils工具類:

package com.wen.utils;

public class PageUtils {
    private static final int BEFORE_CUR=3;//當前頁之前頁數 m,也可以用配置檔案匯入
    private final static int AFTER_CUR=2;//當前頁之後頁數 n,也可以用配置檔案匯入

    public static int[] showPage(int curPage,int totalPage){
        int start=1;//起始頁預設1頁
        int end=totalPage;//結束頁預設為總頁數
        if(totalPage>BEFORE_CUR+AFTER_CUR+1){//如果無法顯示全部頁數
            if(totalPage-curPage<=AFTER_CUR){//如果當前頁太靠近尾頁,則從尾頁往前展示m+n+1條
                end=totalPage;
                start=totalPage-(BEFORE_CUR+AFTER_CUR);
            }else if(curPage<=BEFORE_CUR){//如果當前頁太靠近首頁,則從首頁往後展示m+n+1條
                end=BEFORE_CUR+AFTER_CUR+1;
            }else {//若位於中間位置,則顯示前m後n條
                start=curPage-BEFORE_CUR;
                end=curPage+AFTER_CUR;
            }
        }
        return new int[]{start,end};
    }

}

dao層資料庫訪問部分:

public List<Contact> findContactLimit(int startIndex, int pageSize,Contact contact) {
        sql="select * from contact where 1=1 ";//初始sql語句,加入where 1=1 不影響結果,但方便拼接查詢條件

        ArrayList<Object> strings = new ArrayList<>();
        if(contact.getName()!=null&& !contact.getName().equals("")){//拼接查詢條件
            sql+=(" and name REGEXP ? ");
            strings.add(contact.getName());
        }
        if(contact.getAge()!=0){//拼接查詢條件
            sql+=("and age=? ");
            strings.add(contact.getAge());
        }
        sql+="limit ?,? ";//憑藉分頁語句
        strings.add(startIndex);
        strings.add(pageSize);
        Object[] objects = strings.toArray();

        return jdbcTemplate.query(sql,new BeanPropertyRowMapper<>(Contact.class),objects);
    }

Tips1: 用拼接的sql語句做查詢時, 每個拼接部分後均接一個空格, 有利於減少sql語句錯誤;

Tips2: 編寫Web專案時, 根據資料的流向來依次編寫相關前後端程式碼,是個不錯的思路, 本例中即從前端到後端, 再到前端;

Tips3: 在Tips2中,編寫每一步程式碼完成後, 利用控制檯輸出 和瀏覽器抓包等方式測試資料是否正確 "流動到下一環節", 是一個減少最終成品專案Bug數量的不錯思路;

Tips4: 當在Java程式碼中無法獲取到sql查詢結果時, 複製相關sql語句, 直接進入資料庫管理系統直接執行, 以確認是sql語句還是Java程式碼的問題, 有助於提高DeBug效率;

Tips5: 在前後端的值傳遞過程中無法獲取到值時, 先使用英文值, 再使用中文值試驗,可以排除編碼相關問題;

歡迎補充......