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="width: 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="width: 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。

  最终效果如下:

  

  

 

 

 

 

 

 

           

 

posted @ 2016-03-13 19:20  fjdingsd  阅读(7842)  评论(0编辑  收藏  举报