案例分析:
前端-->后台:
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: 在前后端的值传递过程中无法获取到值时, 先使用英文值, 再使用中文值试验,可以排除编码相关问题;
欢迎补充......