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">«</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">«</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">»</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">»</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: 在前後端的值傳遞過程中無法獲取到值時, 先使用英文值, 再使用中文值試驗,可以排除編碼相關問題;
歡迎補充......