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

欢迎补充......

猜你喜欢

转载自blog.csdn.net/weixin_42711325/article/details/83038032