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