• JDBC使用数据库来完成分页功能


      本篇讲诉如何在页面中通过操作数据库来完成数据显示的分页功能。当一个操作数据库进行查询的语句返回的结果集内容如果过多,那么内存极有可能溢出,所以在大数据的情况下分页是必须的。当然分页能通过很多种方式来实现,而这里我们采用的是操作数据库的方式,而且在这种方式中,最重要的是带限制条件的查询SQL语句:

    select name from user limit m,n

      其中m与n为数字。n代表需要获取多少行的数据项,而m代表从哪开始(以0为起始),例如我们想从user表中先获取前五行数据项(1-5)的name列数据,则SQL为:

    select name from user limit 0,5;

      那么如果要继续往下看一页五行的数据项(6-10)则下一步的SQL应该为:

    select name from user limit 5,5;

      再下一页五行的数据项(11-15)的SQL就为:

    select name from user limit 15,5;

    。。。

      如果对上面的SQL语句不熟悉的话,请先查询相关文档再来看本篇内容。

      我们先来看看“百度贴吧”中的分页效果:

      

      

      

      我选取了首页、次页和尾页三种情况的显示效果,可以看到这个分页显示的效果是比较灵活多变的,开发者可以依据自己的爱好进行展示,但是实质是不变的。

     

      那么接下来我们将做出如下效果的页面显示:

      

      在这个页面中,其实按面向对象的思考方式,这个页面就是一个对象,稍后我们会说到,先来看看在该页面下方的页码分页显示,探究当用户点击之后,分页请求是如何一步步到底层的。

    基本流程就如上图所示,那么我们来分析这个过程:

      第一步:当用户在页面上点击某一页,或者下一页、上一页等等这些超链接,根据MVC设计模式,这些请求都是交给Servlet处理。

      第二步:在Servlet中,首先应该将请求对象带来的信息封装到对象中,由于我们是要查询数据库,因此必须封装成一个查询的条件对象,这里举例为“QueryInfo”自定义对象,在刚对象中包含当前页、每页多少条数据、等会查询数据库从哪开始等信息,只有拥有了这些信息,才能在数据库查询的时候能根据顺序往下翻页。

      第三步、第四步:根据web工程的三层设计模式,业务从service层一步步到dao层。

      第五步、第六步:通过上层传下来的QueryInfo对象,根据里面封装的信息开始对数据库进行操作,使用select name from user limit startIndex,pageSize 这样的SQL命令,将查询到的结果集返回给dao层。

      第七步:dao层根据从数据库返回的结果集,提取出用户想看的页面数据,这里我们将页面数据都封装到一个集合中,除此之外为了之后在页面上能显示页码之类的数据,还必须要获取到查询的总记录数。前面这两个信息数据我们以“QueryResult”自定义对象来封装。

      第八步、第九步:在service层,通过dao传递上来的查询结果“QueryResult”对象,和一开始的查询信息“QueryInfo”对象,来构建页面显示信息,例如页面数据、总记录数、总页数、当前页、上一页、下一页、页码条等等,我们将其都封装进“PageBean”这个JavaBean中,对于JSP中要显示的动态数据,我们只需要提取PageBean对象中的属性即可。其实PageBean的对象需要哪些属性,只要看在JSP页面中我们想显示什么数据就行了,设计还是很简单。

      第十步、第十一步:service层将页面所需要的信息封装进PageBean对象后,将其传给web层的Servlet,由MVC模式,Servlet再将PageBean对象封装进请求交给JSP来显示。

     

      了解完一个分页功能的实现流程之后,下面我将开始进行分页的实现。

      上面的步骤涉及到三个实体对象,分别是QueryInfo,QueryResult、PageBean,而我们在工程中先构建这三个实体,在这三个实体中,有些属性是可以根据别的属性计算出来的,我们没必要提供setter方法。

    实体QueryInfo对象:

     1 public class QueryInfo {
     2     private int currentPage = 1;   //用户当前看的页数
     3     private int pageSize = 10;        //每页多少条显示数据
     4     private int startIndex;            //记住用户想看的页的数据在数据库的起始位置
     5     
     6     。。。  //此处省略currentPage和pageSize两个属性的set和get方法
     7 
     8     public int getStartIndex() {
     9         this.startIndex = (this.currentPage-1)*this.pageSize;
    10         return startIndex;
    11     }
    12 }
    View Code

      注:在查询信息对象QueryInfo中,currentPage和pageSize属性都设置了默认值,如果用户没有特意设置每页显示多少条数据,则根据默认值进行计算。另外由于startIndex属性可以由另外两个属性计算出,因此无需set方法。

    实体PageBean对象:

     1 public class PageBean {
     2     private List contentData;    //保存页面数据
     3     private int totalRecords;    //查询到的总记录数
     4     private int currentPage;       //用户当前看的页数
     5     private int pageSize;        //每页多少条显示数据
     6     private int totalPages;        //总页数
     7     private int previousPage;    //上一页
     8     private int nextPage;        //下一页
     9     private int[] pageBar;        //页码条
    10 
    11 //1,contentData可以从QueryResult对象中获取
    12     。。。//此处省略contentData属性的set和get方法
    13     
    14     //2,totalRecords可以从QueryResult对象中获取
    15     。。。//此处省略contentData属性的set和get方法
    16 
    17     //3,currentPage可以从QueryInfo对象中获取
    18     。。。//此处省略contentData属性的set和get方法
    19 
    20     //4,pageSize可以从QueryInfo对象中获取
    21 。。。//此处省略contentData属性的set和get方法
    22 
    23 //5,总页数可以由总页数和页面数据大小这两个属性计算,因此无需set方法
    24     public int getTotalPages() {
    25         if(totalRecords % pageSize ==0){
    26             totalPages = totalRecords / pageSize;
    27         }else{
    28             totalPages = totalRecords / pageSize + 1;
    29         }
    30         return totalPages;
    31     }
    32 
    33 //6,上一页可以根据当前页计算,因此无需set方法
    34     public int getPreviousPage() {
    35         if(currentPage == 1) {
    36             previousPage = 1;
    37         }else {
    38             previousPage = currentPage - 1;
    39         }
    40         return previousPage;
    41     }
    42 
    43     //7,下一页可以根据当前页计算,因此无需set方法
    44     public int getNextPage() {
    45         if(currentPage == totalPages) {
    46             nextPage = totalPages;
    47         }else {
    48             nextPage = currentPage + 1;
    49         }
    50         return nextPage;
    51     }
    52 
    53     //8,页码条可以由总页数来计算显示,因此无需set方法
    54     public int[] getPageBar() {
    55         pageBar = null;
    56         int startIndex ;
    57         int endIndex ;
    58         if(totalPages<10) {
    59             pageBar = new int[totalPages];
    60             startIndex = 1;
    61             endIndex = totalPages;
    62             
    63         }else{
    64             pageBar = new int[10];
    65             startIndex = currentPage-5;
    66             endIndex = currentPage+4;
    67             if(startIndex<1) {
    68                 startIndex = 1;
    69                 endIndex = 10;
    70             }
    71             if(endIndex>totalPages) {
    72                 startIndex = totalPages-10+1;
    73                 endIndex = totalPages;
    74             }
    75         }
    76         int index = 0;
    77         for(int i=startIndex;i<=endIndex;i++) {    
    78             pageBar[index] = i;
    79             index++;
    80         }
    81         return pageBar;
    82     }}
    View Code

    注:PageBean对象属性会比较多,因为这些属性都是要在页面上显示的内容。虽然属性多,但是由很多属性值可以通过别的属性计算得到,另外的属性可以通过别的对象属性得到。

      尤其是页码条pageBar这个属性,这里我的设计是,如果总页数不超过10页的话,那么页码条显示的个数就为总页数;如果总页数超过10页,那么页码条固定显示10个页码,同时如果当前页在最前6个页则页码条保持不变,在中间部分的当前页会保持在页码条的中间位置(前面5个页码,后面4个页码,当前页在第6个位置)。如果当前页到最后部分也是同理。

     

      上面三个对象设计完成后,我们就要来考虑在分页流程中不同层对查询信息的处理方式。

      按从下到上的开发流程,首先是dao层,该层必须通过请求发来的查询信息来对数据库进行操作,也就是本文最开始讲解的SQL语句的两个参数是执行数据库操作的关键,本文以显示User用户为分页案例,在数据库中为user表。因此在处理User对象的dao层实现类UserDaoImpl中,查询方法为pageQuery,返回上面刚刚定义的QueryResult对象。

      注:该工程是博客《JDBC操作数据库的学习(2)》和《在JDBC中使用PreparedStatement代替Statement,同时预防SQL注入》中工程的扩展,下面使用到JDBC的工具类JdbcUtils即是在《JDBC操作数据库的学习(2)》中的定义。

      下面的代码对应流程图中的第五、六、七步骤:

     1 package com.fjdingsd.dao.impl;
     2 public class UserDaoImpl implements UserDao {
     3     public QueryResult pageQuery(int startIndex,int pageSize) {
     4         Connection conn = null;
     5         PreparedStatement st = null;
     6         ResultSet rs = null;
     7         QueryResult result = new QueryResult();
     8         try{
     9             conn = JdbcUtils.getConnection();
    10             String sql = "select * from user limit ?,?";
    11             st = conn.prepareStatement(sql);
    12             st.setInt(1, startIndex);
    13             st.setInt(2, pageSize);
    14             rs = st.executeQuery();
    15             List contentList = new ArrayList();
    16             while(rs.next()) {
    17                 User user = new User();
    18                 user.setId(rs.getInt("id"));
    19                 user.setName(rs.getString("name"));
    20                 user.setAge(rs.getInt("age"));
    21                 contentList.add(user);
    22             }
    23             result.setContentData(contentList);
    24             //获取了页面数据后还没结束,还得获取总记录数
    25             sql = "select count(*) from user";
    26             st = conn.prepareStatement(sql);
    27             rs = st.executeQuery();
    28             if(rs.next()) {
    29                 int totalRecords = rs.getInt(1);  //rs.getInt("count(*)")也是可以的
    30                 result.setTotalRecords(totalRecords);
    31             }
    32             return result;    
    33         }catch (Exception e) {
    34             throw new RuntimeException(e);
    35         }finally{
    36             JdbcUtils.release(conn, st, rs);
    37         }
    38     }
    39 }
    View Code

      上面在dao层对User对象处理的实现类UserDaoImpl已经处理好了分页查询,该pageQuery方法返回的QueryResult对象正是在service层中处理User对象的业务的方法所需要的参数。在service层中,我们需要根据查询得到的结果QueryResult对象,来获取页面显示所需要的对象PageBean。

      下面的代码对应流程图的第三、四和第八、九步骤:

     1 package com.fjdingsd.service;
     2 public class UserServiceImpl {
     3     private UserDao userDao = new UserDaoImpl(); //通常使用工程模式获取实现类对象,这里为了简便直接采用实现类的构造器
     4     
     5     public PageBean pageQuery(QueryInfo info) {
     6         //获取对应dao的实现类中的查询到的结果数据
     7         QueryResult result = userDao.pageQuery(info.getStartIndex(), info.getPageSize());
     8         
     9         //根据dao的查询结果,生成页面显示需要的PageBean
    10         PageBean page = new PageBean();
    11         page.setContentData(result.getContentData());
    12         page.setTotalRecords(result.getTotalRecords());
    13         page.setCurrentPage(info.getCurrentPage());
    14         page.setPageSize(info.getPageSize());
    15         
    16         return page;
    17     }
    18 }
    View Code

      上面在service层将查询到的结果对象封装成页面显示所需要的对象PageBean,service层需要将这个对象交给web层的Servlet来处理,其实这个Servlet也是最开始处理请求对象的Servlet,因为最开始要想生成查询信息QueryInfo对象就必须要从请求中提取数据封装。

        注:下面代码中使用到了工具类的静态方法WebUtils.request2Bean,是将请求对象中的参数值转移到一个Bean对象中,该方法的实现具体请看《在WEB工程的web层中的编程技巧》。即使Request对象中没有我们需要的参数,那么创建出来的QueryInfo对象中的currentPage和pageSize属性我们在最开始创建时已经设置了默认值,所以无需担心空指针异常。

        下面的代码对应流程图中的第一,二和第十、十一步骤:

     1 package com.fjdingsd.web.controller;
     2 public class UserListServlet extends HttpServlet {
     3 
     4     public void doGet(HttpServletRequest request, HttpServletResponse response)
     5             throws ServletException, IOException {
     6         try{
     7             QueryInfo info = WebUtils.request2Bean(request, QueryInfo.class);
     8             UserService userService = new UserServiceImpl();//通常使用工程模式获取实现类对象,这里为了简便直接采用实现类的构造器
     9 
    10             PageBean page = userService.pageQuery(info);
    11             request.setAttribute("pagebean", page);
    12             request.getRequestDispatcher("/WEB-INF/jsp/userlist.jsp").forward(request, response);
    13         }catch (Exception e) {
    14             e.printStackTrace();
    15             request.setAttribute("message", "查看用户失败");
    16             request.getRequestDispatcher("/message.jsp").forward(request, response);
    17         }
    18     }
    19 }
    View Code

      上面在web层中已经使用Servlet将页面需要显示的信息全部封装进PageBean对象中,通过请求对象Request存储,最后转发进相应的JSP页面,这里例子为userlist.jsp页面,最后只要在这个页面中将请求对象中保存的PageBean对象提取出来,再将该对象中的每个属性的内容在页面相应的地方显示即可。

      在JSP页面中,我们以表格的形式将页面数据显示出来,除了用户想看的页面数据以外,其他的就是与页码相关的,因为在Servlet中我们将PageBean对象封装进请求Request对象中,所以在JSP页面中我们就可以通过EL表达式将其取出,而是会是大量地使用到EL表达式和JSP标签。

     1 <body>
     2         <a href="${pageContext.request.contextPath}/servlet/UserListServlet">显示用户</a><br>       
     3         <table>
     4             <tr>
     5                 <td>用户id</td>
     6                 <td>用户姓名</td>
     7                 <td>用户年龄</td>
     8             </tr>
     9             <c:forEach var="user" items="${requestScope.pagebean.contentData }">
    10                 <tr>
    11                 <td>${user.id}</td>
    12                 <td>${user.name}</td>
    13                    <td>${user.age}</td>
    14             </tr>
    15             </c:forEach>        
    16         </table>
    17         <br>
    18   <%-- 
    19     共${pagebean.totalRecords}    条记录,每页${pagebean.pageSize}条,共${pagebean.totalPages}页,
    20     当前第${pagebean.currentPage}页  &nbsp;&nbsp;
    21    <a href="${pageContext.request.contextPath}/servlet/UserListServlet?currentPage=${pagebean.currentPage}">上一页</a>
    22            <c:forEach var="bar" items="${requestScope.pagebean.pageBar}">
    23                <a href="${pageContext.request.contextPath}/servlet/UserListServlet?currentPage=${pagebean.currentPage}">${bar}</a>
    24            </c:forEach>
    25    <a href="${pageContext.request.contextPath}/servlet/UserListServlet?currentPage=${pagebean.currentPage}">下一页</a>
    26    --%>    
    27    
    28     共 ${pagebean.totalRecords}    条记录,
    29     每页<input type="text" id="pagesize" value="${pagebean.pageSize }" onchange="gotopage(1)" style=" 30px" maxlength="3">条,
    30     共${pagebean.totalPages}页,
    31     当前第${pagebean.currentPage}页  &nbsp;&nbsp;
    32    <a href="javascript:void(0)" onclick="gotopage(${pagebean.previousPage})" >上一页</a> &nbsp;
    33            <c:forEach var="bar" items="${requestScope.pagebean.pageBar}">
    34                <a href="javascript:void(0)" onclick="gotopage(${bar})" >${bar}</a>&nbsp;
    35            </c:forEach>
    36    <a href="javascript:void(0)" onclick="gotopage(${pagebean.nextPage})" >下一页</a> &nbsp;
    37    
    38    跳转<input type="text" id="forwardPage" value="${pagebean.currentPage}" style=" 30px;" onchange="gotopage(this.value)">39    
    40   </body>
    41   
    42   <script type="text/javascript">
    43       function gotopage(wantedPage) {
    44           var pagesize = document.getElementById("pagesize").value;
    45           window.location.href = "${pageContext.request.contextPath}/servlet/UserListServlet?currentPage="+wantedPage+"&pageSize="+pagesize;
    46           
    47       }
    48 
    49   </script>
    View Code

      在上面的代码中,我们使用了JSTL标签库的<c:forEach>标签来迭代页面数据内容,也就是PageBean中的contentData集合。中间有一段的代码虽然被注释掉了,这里是用URL地址的方法给每个<a>标签中的href属性赋值超链接,在后面的代码中我使用的是JavaScript的方式。

      在Servlet中跳转到JSP页面的请求对象中设置了PageBean对象的关键字:request.setAttribute("pagebean", page); 因此在JSP中,使用EL表达式将以“pagebean”为关键字,而后面跟着PageBean对象的属性取出对应的值。

      在JavaScript中,上面无论是改变页面大小、上一页、下一页,某个特定页,跳转某页,都是根据gotopage方法来讲请求超链接发送给Servlet,再一步步发送到数据库查询。在gotopage方法中,传入参数是“wantedPage”,是用户想要去的页数,同时每次该方法调用还会获取页面大小“pagesize”,将这两个数放置在URL地址后作为请求参数给Servlet。

      最终效果如下:

      

      

               

  • 相关阅读:
    CSS3实现轮播切换效果
    angularjs directive
    angularjs 迭代器
    anjularjs 路由
    sublime text3 快捷键设置
    如何使用git 跟进项目进程
    hdu 4842(NOIP 2005 过河)之 动态规划(距离压缩)
    叠箱子问题 之 动态规划
    华为oj 之 蜂窝小区最短距离
    华为oj 之 整数分隔
  • 原文地址:https://www.cnblogs.com/fjdingsd/p/5272828.html
Copyright © 2020-2023  润新知