本篇講訴如何在頁面中通過操作數據庫來完成數據顯示的分頁功能。當一個操作數據庫進行查詢的語句返回的結果集內容如果過多,那么內存極有可能溢出,所以在大數據的情況下分頁是必須的。當然分頁能通過很多種方式來實現,而這里我們采用的是操作數據庫的方式,而且在這種方式中,最重要的是帶限制條件的查詢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 }
注:在查詢信息對象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 }}
注: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 }
上面在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 }
上面在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 }
上面在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}頁 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}頁
32 <a href="javascript:void(0)" onclick="gotopage(${pagebean.previousPage})" >上一頁</a>
33 <c:forEach var="bar" items="${requestScope.pagebean.pageBar}">
34 <a href="javascript:void(0)" onclick="gotopage(${bar})" >${bar}</a>
35 </c:forEach>
36 <a href="javascript:void(0)" onclick="gotopage(${pagebean.nextPage})" >下一頁</a>
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>
在上面的代碼中,我們使用了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。
最終效果如下: