美文网首页
手把手教你做JavaWeb项目:多条件过滤

手把手教你做JavaWeb项目:多条件过滤

作者: 叩丁狼教育 | 来源:发表于2018-11-12 16:18 被阅读508次

    作者:叩丁狼教育,钟昕灵老师。原创文章,转载请注明出处

    分页查询

    需求分析:在列表页面中,显示指定条数的数据,通过翻页按钮完成首页/上一页/下一页/尾页的查询

    数据分析:

    通过观察,页面上需要显示下面的几个数据:
    当前页:currentPage
    页面大小:pageSize
    总页数:totalPage
    首页:1
    上一页:prevPage
    下一页:nextPage
    尾页:endPage
    总条数:totalCount
    结果集:result


    image.png

    那么,我们应该如何方便快速的将这多个数据共享到页面上呢?答案是:封装

    我们应该将这几个参数封装到一个对象中,然后共享这个对象即可,所以,我们有了下面这个类

    @Getter
    public class PageResult {
        public static final  PageResult EMPTY_RESULT = new PageResult(Collections.EMPTY_LIST, 0, 1, 3);
        //1:两传
        private int currentPage;
        private int pageSize;
        
        //2:两查
        private List<?> result;
        private int totalCount;
        
        //3:三计算
        private int prevPage;
        private int nextPage;
        private int endPage;
        
        public PageResult(List<?> result, int totalCount, int currentPage, int pageSize){
            
            this.result = result;
            this.totalCount = totalCount;
            this.pageSize = pageSize;
            this.currentPage = currentPage;
            //计算
            this.endPage = totalCount % pageSize == 0 ?
                        totalCount / pageSize : totalCount / pageSize  + 1; 
            this.prevPage = currentPage - 1 > 0 ? currentPage - 1 : 1;
            this.nextPage = currentPage + 1 > endPage ? endPage :currentPage + 1;
        }
    }
    

    在这个类中,我们提供了一个构造器来快速封装数据
    其中,endPage/prevPage/nextPage是通过上面的几个参数计算得来的

    在这些数据中,存在两个需要从数据库中查询得到的数据:总条数/结果集
    这两个数据我们需要下面两条SQL进行查询

    查询部门表中数据的总条数
    SELECT count(id) FROM department
    使用LIMIT关键字查询指定页面的数据
    SELECT  id, name, sn FROM department LIMIT #{start}, #{pageSize}
    
    #{start}:         使用(currentPage-1)*pageSize表达式计算出来的开始索引
    #{pageSize}: 每次查询的最大条数
    

    要执行这两条SQL,需要用户传递两个参数:currentPage和pageSize
    为了参数方便传递,我们将这两个参数封装到一个类中:QueryObject

    @Setter
    @Getter
    public class QueryObject {
            // 默认查询第一页的数据
        private int currentPage = 1;
            // 页面中默认显示10条数据
        private int pageSize = 5;
        public int getStart(){
            return (currentPage - 1)*pageSize;
        }
    }
    

    可以看出,查询结果集中的#{start}表达式,是访问查询对象中的getStart()方法来获取到计算得到的开始索引

    到此,我们都已经封装好了分页查询中最核心的两个类:
    QueryObject:封装用户传递过来的currentPage/pageSize
    PageResult:封装页面上显示需要的result/totalCount/currentPage/pageSize/totalPage/prevPage/pageSize

    有了这两个类,我们就可以在service中定义下面的方法,来处理分页查询的业务了:

    
        public PageResult query(QueryObject qo) {
            //查询表中数据的总条数
            int totalCount = dao.queryForCount(qo);
            //当查询到的总条数为0时,说明没有数据,此时就不应该再之后下面的查询
            //直接返回相应的默认值即可
            if (totalCount == 0) {
                return PageResult.EMPTY_RESULT;
            }
            List<Department> data = dao.queryForList(qo);
            PageResult result = new PageResult(data, totalCount, qo.getCurrentPage(), qo.getPageSize());
            return result;
        }
    

    该方法接收用户传递的数据(QueryObject),返回用户需要的数据(PageResult)
    通过调用dao中的两个方法执行两条SQL查询数据(总条数和结果集)

    <!--查询总条数-->
    <select id="queryForCount" resultType="java.lang.Integer">
            SELECT count(id)
            FROM department
        </select>
    <!--查询结果集-->
        <select id="queryForList" resultType="Department">
            SELECT
                id,
                name,
                sn
            FROM department
            LIMIT #{start}, #{pageSize}
        </select>
    

    然后将数据封装到PageResult对象中返回给表现层

    表现层获取到service中封装的PageResult对象后,共享到request作用域中
    然后请求转发到list.jsp页面

    protected void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            //接收用户传递的currentPage和pageSize
            String currentPage = req.getParameter("currentPage");
            String pageSize = req.getParameter("pageSize");
            //将数据封装到QueryObject中,传递给service进行处理
            QueryObject qo = new QueryObject();
            if(!StringUtils.isNullOrEmpty(currentPage)){
                qo.setCurrentPage(Integer.valueOf(currentPage));
            }
            if(!StringUtils.isNullOrEmpty(pageSize)){
                qo.setPageSize(Integer.valueOf(pageSize));
            }
            PageResult result = service.query(qo);
            //共享获取到的PageResult对象
            req.setAttribute("result", result);
            req.setAttribute("qo", qo);
            //请求转发回到list.jsp页面
            req.getRequestDispatcher("/WEB-INF/views/department/list.jsp").forward(req, resp);
        }
    

    在list.jsp页面上,使用EL+JSTL获取数据并显示在对应的位置
    部门列表

    <c:forEach items="${result.data}" var="entity" varStatus="vs">
      <tr>
          <td>${vs.count}</td>
          <td>${entity.name}</td>
          <td>${entity.sn}</td>
          <td>
              <a class="btn btn-info btn-xs" href="/department?cmd=input&id=${entity.id}">
                 <span class="glyphicon glyphicon-pencil"></span>编辑
             </a>
             <a href="/department?cmd=delete&id=${entity.id}" class="btn btn-danger btn-xs">
               <span class="glyphicon glyphicon-trash"></span>删除
             </a>
         </td>
      </tr>
    </c:forEach>
    

    分页条

    <div style="text-align: center;">
         <a href="javascript:;" onclick="goPage(1)">首页</a>
         <a href="javascript:;" onclick="goPage(${result.prevPage})">上一页</a>
         <a href="javascript:;" onclick="goPage(${result.nextPage})">下一页</a>
         <a href="javascript:;" onclick="goPage(${result.endPage})">尾页</a>
    
         当前页: ${result.currentPage} / ${result.endPage}
    
         跳转到第: <input id="currentPage" name="currentPage" style="width: 80px; text-align: center;"
                      type="number" min="1" max="${result.endPage}" value="${result.currentPage}"/> 页
         <input type="submit" value="GO"/>
         每页显示:
         <select name="pageSize" onchange="goPage(1);">
             <option ${qo.pageSize==5?"selected='selected'":""}>5</option>
             <option ${qo.pageSize==10?"selected='selected'":""}>10</option>
             <option ${qo.pageSize==15?"selected='selected'":""}>15</option>
             <option ${qo.pageSize==20?"selected='selected'":""}>20</option>
         </select>
         条
    </div>
    

    效果如下


    image.png

    说明:
    在点击翻页的时候,通过执行相应的JS代码提交表单来发起请求
    将需要查询的当前页的值设值给表单中的id为currentPage的输入框,然后提交表单
    目的主要是和后面的高级查询进行合并使用

    做到这里,我们部门的分页功能就完成了
    因为部门的字段比较少,所以,在这个模块中,没有设计高级查询的功能,这个功能我们在员工模块中再去实现
    接下来,我们来看看员工模块相应功能的实现


    在完成部门的CRUD和分页查询后发现,其他模块的这些功能基本相似
    不同之处主要在于字段不同而已,所以,在这里,我们主要对这些不同点进行说明,其他的按照前面的实现即可

    首先,来看看员工的表结构


    image.png

    在该表中,前六个字段都是基本的字段,第七个(dept_id),这个字段是关联部门的外键列
    所以,待会儿我们在完成CRUD的过程中,需要注意维护该字段的值

    清楚表结构之后,我们来对员工的CRUD做一个简单的分析

    查询功能:

    image.png

    可以看到,处理部门这一列显示的数据比较特殊之外,其他的都是基本的数据展示而已
    什么是特殊?什么是不特殊?
    员工除了部门的信息外,其他的数据都是直接来自于数据库,而部门在表中只存储了对应的编号,但是页面上需要显示部门的名称,那么这个问题我们是怎么解决的呢?

    好,首先解释一下,这个问题的解决方案在目前我们的web阶段还没有涉及过,所以,我在这只能点到为止

    我们的解决方案是:在执行该数据的查询的时候,使用多表查询,将员工及其所在部门的信息查询出来,SQL如下:

    SELECT
                e.id,
                e.name,
                e.password,
                e.email,
                e.age,
                e.admin,
                e.dept_id,
                d.id   d_id,
                d.name d_name,
                d.sn   d_sn
    FROM employee e LEFT JOIN department d on e.dept_id = d.id
    limit #{start}, #{pageSize}
    

    这条SQL能够查询到如下的结果


    image.png

    然后在resultMap中如下的配置,完成数据的封装,员工相关的数据封装到Employee对象中,部门相关的数据封装到Department对象中

    <resultMap id="BaseResultMap" type="Employee">
            <id column="id" jdbcType="BIGINT" property="id"/>
            <result column="name" jdbcType="VARCHAR" property="name"/>
            <result column="password" jdbcType="VARCHAR" property="password"/>
            <result column="email" jdbcType="VARCHAR" property="email"/>
            <result column="age" jdbcType="INTEGER" property="age"/>
            <result column="admin" jdbcType="BIT" property="admin"/>
            <!-- 一对多关系 -->
            <association property="dept" javaType="Department">
                <id column="d_id" property="id"/>
                <result column="d_name" property="name"/>
                <result column="d_sn" property="sn"/>
            </association>
        </resultMap>
    

    最后,在select元素中使用resultMap来完成映射

    <select id="queryForList" resultMap="BaseResultMap">
    

    完成这些操作之后,我们获取到的每个员工及其所在的部门信息就封装好了,那么,在JSP页面中使用EL表达式,就能够获取到当前员工所在部门的相关信息了

    <td>${entity.dept.name}</td>
    

    查询功能分析到此结束,其他功能和部门的一致

    新增功能:

    需求分析:
    保存用户的相关信息,包括用户所在部门的信息
    通过对员工表的观察发现,表中关联了部门的主键信息,来说明当前员工所在的部门
    所以,在保存员工的时候,需要为员工设置所在的部门.页面设计如下


    image.png

    为员工设置所在的部门,用户只需要进行选择即可,然后将选中部门的id传递到服务端
    那么问题来了,如何将部门id通过下拉框传递到服务端呢?请看下面的分析

    protected void input(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            List<Department> departments = departmentService.list();
            req.setAttribute("departments", departments);
            String id = req.getParameter("id");
            if (!StringUtils.isNullOrEmpty(id)) {
                Employee employee = service.get(Long.valueOf(id));
                req.setAttribute("employee", employee);
            }
            req.getRequestDispatcher("/WEB-INF/views/employee/input.jsp").forward(req, resp);
        }
    
    <select class="form-control" id="dept" name="deptId">
         <c:forEach var="d" items="${departments}">
             <option value="${d.id}" >${d.name}</option>
         </c:forEach>
     </select>
    

    首先,在跳转到该页面之间,我们先将所有的部门信息查询到
    然后在页面上循环遍历,生成对应的option元素,将部门的id作为option的value(提交的数据),将部门的name作为option的文本内容(显示的数据)
    最后,在用户选择对应选项后,提交表单,会将对应option元素的value提交到后台,完整数据如下:


    image.png

    通过上图,可以清晰的看到,我们已经将完整的数据提交到后台
    后台获取到这些数据之后,就能够将其保存到数据库中
    那么,我们的保存功能就算完成了

    更新功能:

    更新和保存只有一个地方不同,就是需要数据回显
    而数据回显中,我们只对部门(下拉框)和超级管理员(复选框)做一个说明,其他的因为都是普通的input元素,使用value属性显示数据即可
    而下拉框和复选框需要单独进行处理,如下:

    <select class="form-control" id="dept" name="deptId">
        <c:forEach var="d" items="${departments}">
            <option value="${d.id}" ${employee.dept.id==d.id?"selected":""}>${d.name}</option>
        </c:forEach>
    </select>
    
    <input type="checkbox" id="admin" name="admin" ${employee.admin?'checked':''}>
    

    这里,我们选择使用EL表达式的三元运算符进行判断,为下拉框添加selected属性,为复选框添加checked属性

    编辑的时候,数据能回显,接下来的操作和新增一致

    删除功能

    和部门的删除一致,这里就不再赘述

    到此,员工的CRUD结束

    高级查询

    功能需求:
    输入关键字和部门信息进去过滤查询,关键字是根据姓名和邮箱两个字段查询

    页面设计


    高级查询效果图

    当用户输入关键字"赵"和部门"总经办"后,在列表中显示的查询结果则为所有总经办姓赵的员工

    那么此时应该执行下面的SQL来查询相应的数据

    SELECT 
      e.id, e.name, e.password, e.email, e.age, e.admin, e.dept_id, d.id d_id, d.name d_name, d.sn d_sn 
    from 
      employee e 
    LEFT JOIN 
      department d 
    ON 
      e.dept_id = d.id 
    WHERE 
      (e.name LIKE concat('%',? ,'%') OR e.email LIKE concat('%',? ,'%')) AND e.dept_id = ? LIMIT ?, ? 
    

    在该SQL中,WHERE后面的条件是根据用户传递的高级查询相关的参数拼接而来

    这里,我们需要解决两个问题:
    1.这里的多个高级查询的参数如何传递?
    对于这个问题,我们应该能够比较快的想到解决方案---数据封装,如下:

    @Setter
    @Getter
    public class EmployeeQueryObject extends QueryObject {
        private String keyword;  //按照员工名称与邮箱模糊查询
        private Long deptId = -1L;  //按照部门id查询
        
        //当keyword为null或者空字符串的时候,都视为null处理
        public String getKeyword(){
            return StringUtils.isEmpty(keyword) ? null : keyword;
        }
    }
    

    2.参数拿到后,如何拼接到对应的SQL中?
    使用mybatis中的动态SQL中提供的标签,在mapper映射文件中进行SQL的拼接

    <sql id="base_where">
        <where>
            <if test="keyword != null">
                AND( e.name LIKE concat('%',#{keyword} ,'%') OR e.email LIKE concat('%',#{keyword} ,'%'))
            </if>
            <if test="deptId > 0">
                AND e.dept_id = #{deptId}
            </if>
        </where>
    </sql>
    

    以上两个问题解决后,我们就可以根据用户传递过来的参数,执行对应的过滤查询的SQL
    最后,和分页查询的逻辑一样,将数据封装到PageResult中,和分页相关的数据一同返回到页面
    效果如[高级查询效果图]所示

    好了,基本功能是完成了,我们再来看看下面的问题:
    首先,在表单中输入下面的参数,然后查询


    image.png

    然后点击下一页进行翻页


    image.png
    可以看到,在点击翻页之后,我们不是在上面的基础上查询下一页的数据,而是查询到了所有的数据,WHY?
    其实很简单,来看看我们的请求参数,一切就清楚了
    在我们点击翻页的时候,传递的参数只有currentPage,并没有将之前的高级查询的参数一起传递到后台,所以执行了下面的SQL查询到上面的结果
    SELECT 
      e.id, e.name, e.password, e.email, e.age, e.admin, e.dept_id, d.id d_id, d.name d_name, d.sn d_sn 
    from 
      employee e 
    LEFT JOIN 
      department d 
    on 
      e.dept_id = d.id limit ?, ? 
    

    所以,要想在之前高级查询的基础上,继续进行分页查询,那么我们只有一个办法,就是在翻页的时候将高级查询和分页的参数一起提交到后台,拼接执行相应的SQL才行

    解决方案:
    使用JS来完成数据的提交(JS在目前阶段还未进行系统学习,所以,这里大家重点应该是放在我们要做的事情上,而不是怎么做)

    <script type="text/javascript">
        function goPage(currentPage) {
            //为表单中的currentPage输入框设值
            document.getElementById("currentPage").value = currentPage
            //提交表单
            document.forms[0].submit();
        }
    </script>
    
    
    翻页条
    <a href="javascript:;" onclick="goPage(1)">首页</a>
    <a href="javascript:;" onclick="goPage(${result.prevPage})">上一页</a
    <a href="javascript:;" onclick="goPage(${result.nextPage})">下一页</a
    <a href="javascript:;" onclick="goPage(${result.endPage})">尾页</a>
    

    在点击翻页按钮时,调用定义好的goPage函数,完成其中的两件事即可
    此时,我们可以看到问题已然解决

    最后,页面跳转和页面大小的设置功能,我们不做要求,如果要实现也很简单,我这里就直接把相关代码贴出来

    跳转到第: <input id="currentPage" name="currentPage" style="width: 80px; text-align: center;"
                 type="number" min="1" max="${result.endPage}" value="${result.currentPage}"/> 页
    <input type="submit" value="GO"/>
    每页显示:
    <select name="pageSize" onchange="goPage(1);">
        <option ${qo.pageSize==5?"selected='selected'":""}>5</option>
        <option ${qo.pageSize==10?"selected='selected'":""}>10</option>
        <option ${qo.pageSize==15?"selected='selected'":""}>15</option>
        <option ${qo.pageSize==20?"selected='selected'":""}>20</option>
    </select>
    条
    

    好了,高级查询的功能实现到此结束,我们来做一个小结
    高级查询和分页的功能,我们应该重点分析两个点
    1.用户需要看到什么数据?
    2.这些数据应该执行什么样的SQL才能查询到?
    如果将这两个问题分析清楚了,那么大家就能够知道这个过程中所封装的几个类的作用了
    QueryObject:封装查询对象中通用的属性
    EmployeeQueryObject:封装高级查询相关的条件参数
    PageResult:封装用户需要使用到的数据

    所以实现步骤大致如下:
    1.获取到用户传递的高级查询和分页的参数,封装到对象的查询对象中
    2.从查询对象中取出数据,然后拼接SQL
    3.将查询之后得到的结果,封装到PageResult对象中
    4.页面获取到PageResult中的数据进行显示

    点击查看JavaWeb系列的其它文章

    手把手教你做JavaWeb项目:项目需求分析
    手把手教你做JavaWeb项目:前台界面
    手把手教你做JavaWeb项目:登录模块

    前往叩丁狼官网获取更多技术视频

    相关文章

      网友评论

          本文标题:手把手教你做JavaWeb项目:多条件过滤

          本文链接:https://www.haomeiwen.com/subject/hiecfqtx.html