JSP分頁技術實現
阿新 • • 發佈:2018-12-19
目前比較廣泛使用的分頁方式是將查詢結果快取在HttpSession或有狀態bean中,翻頁的時候從快取中取出一頁資料顯示。這種方法有兩個主要的缺點:一是使用者可能看到的是過期資料;二是如果資料量非常大時第一次查詢遍歷結果集會耗費很長時間,並且快取的資料也會佔用大量記憶體,效率明顯下降。 其它常見的方法還有每次翻頁都查詢一次資料庫,從ResultSet中只取出一頁資料(使用rs.last();rs.getRow()獲得總計錄條數,使用rs.absolute()定位到本頁起始記錄)。這種方式在某些資料庫(如oracle)的JDBC實現中差不多也是需要遍歷所有記錄,實驗證明在記錄數很大時速度非常慢。 至於快取結果集ResultSet的方法則完全是一種錯誤的做法。因為ResultSet在Statement或Connection關閉時也會被關閉,如果要使ResultSet有效勢必長時間佔用資料庫連線。 因此比較好的分頁做法應該是每次翻頁的時候只從資料庫裡檢索頁面大小的塊區的資料。這樣雖然每次翻頁都需要查詢資料庫,但查詢出的記錄數很少,網路傳輸資料量不大,如果使用連線池更可以略過最耗時的建立資料庫連線過程。而在資料庫端有各種成熟的優化技術用於提高查詢速度,比在應用伺服器層做快取有效多了。 在oracle資料庫中查詢結果的行號使用偽列ROWNUM表示(從1開始)。例如select * from employee where rownum<10 返回前10條記錄。但因為rownum是在查詢之後排序之前賦值的,所以查詢employee按birthday排序的第100到120條記錄應該這麼寫: mySQL可以使用LIMIT子句: select name, birthday from employee order by birthday LIMIT 99,20 DB2有rownumber()函式用於獲取當前行數。 SQL Server沒研究過,可以參考這篇文章:http://www.csdn.net/develop/article/18/18627.shtm 在Web程式中分頁會被頻繁使用,但分頁的實現細節卻是程式設計過程中比較麻煩的事情。大多分頁顯示的查詢操作都同時需要處理複雜的多重查詢條件,sql語句需要動態拼接組成,再加上分頁需要的記錄定位、總記錄條數查詢以及查詢結果的遍歷、封裝和顯示,程式會變得很複雜並且難以理解。因此需要一些工具類簡化分頁程式碼,使程式設計師專注於業務邏輯部分。下面是我設計的兩個工具類: PagedStatement 封裝了資料庫連線、總記錄數查詢、分頁查詢、結果資料封裝和關閉資料庫連線等操作,並使用了PreparedStatement支援動態設定引數。 RowSetPage 參考PetStore的page by page iterator模式, 設計RowSetPage用於封裝查詢結果(使用OracleCachedRowSet快取查詢出的一頁資料,關於使用CachedRowSet封裝資料庫查詢結果請參考JSP頁面查詢顯示常用模式)以及當前頁碼、總記錄條數、當前記錄數等資訊, 並且可以生成簡單的HTML分頁程式碼。 PagedStatement 查詢的結果封裝成RowsetPage。 下面是簡單的使用示例 :
- //DAO查詢資料部分程式碼:
- …
- public RowSetPage getEmployee( gender, int pageNo) throws {
- sql="select emp_id, emp_code, user_name, real_name from employee where gender =?";
- //使用Oracle資料庫的分頁查詢實現,每頁顯示5條
- PagedStatement pst =new PagedStatementOracleImpl(sql, pageNo, 5);
- pst.setString(1, gender);
- return pst.executeQuery();
- }
- //Servlet處理查詢請求部分程式碼:
- …
- int pageNo;
- try{
- //可以通過引數pageno獲得使用者選擇的頁碼
- pageNo = .parseInt(request.getParameter("pageno") );
- //預設為第一頁
- pageNo=1;
- }
- gender = request.getParameter("gender" );
- request.setAttribute("empPage", myBean.getEmployee(gender, pageNo) );
- …
- //JSP顯示部分程式碼
- <%@ page import = "page.RowSetPage"%>
- …
- <script language="javascript">
- function doQuery(){
- form1.actionType.value="doQuery";
- form1.submit();
- }
- </script>
- …
- <form name=form1 method=get>
- <input type=hidden name=actionType>
- 性別:
- <input type=text name=gender size=1 value="<%=request.getParameter("gender")%>">
- <input type=button value=" 查詢 " onclick="doQuery()">
- <%
- RowSetPage empPage = (RowSetPage)request.getAttribute("empPage");
- if (empPage == null ) empPage = RowSetPage.EMPTY_PAGE;
- %>
- …
- <table cellspacing="0" width="90%">
- <tr> <td>ID</td> <td>程式碼</td> <td>使用者名稱</td> <td>姓名</td> </tr>
- <%
- javax.sql. empRS = (javax.sql.) empPage.getRowSet();
- if (empRS!=null) while (empRS.next() ) {
- %>
- <tr>
- <td><%= empRS.getString("EMP_ID")%></td>
- <td><%= empRS.getString("EMP_CODE")%></td>
- <td><%= empRS.getString("USER_NAME")%></td>
- <td><%= empRS.getString("REAL_NAME")%></td>
- </tr>
- <%
- }// end while
- %>
- <tr>
- <%
- //顯示總頁數和當前頁數(pageno)以及分頁程式碼。
- //此處doQuery為頁面上提交查詢動作的javascript函式名, pageno為標識當前頁碼的引數名
- %>
- <td colspan=4><%= empPage .getHTML("doQuery", "pageno")%></td>
- </tr>
- </table>
- </form>
- ///////////////////////////////////
- //
- // Page.java
- // author: [email protected]
- //
- ///////////////////////////////////
- package page;
- import java.util.List;
- import java.util.;
- import java.util.;
- import java.util.;
- /**
- * Title: 分頁物件<br>
- * Description: 用於包含資料及分頁資訊的物件<br>
- * Page類實現了用於顯示分頁資訊的基本方法,但未指定所含資料的型別,
- * 可根據需要實現以特定方式組織資料的子類,<br>
- * 如RowSetPage以RowSet封裝資料,ListPage以List封裝資料<br>
- * Copyright: Copyright (c) 2002 <br>
- * @author [email protected] <br>
- * @version 1.0
- */
- public class Page implements java.io. {
- public static final Page EMPTY_PAGE = new Page();
- public static final int DEFAULT_PAGE_SIZE = 20;
- public static final int MAX_PAGE_SIZE = 9999;
- private int myPageSize = DEFAULT_PAGE_SIZE;
- private int start;
- private int avaCount,totalSize;
- private data;
- private int currentPageno;
- private int totalPageCount;
- /**
- * 預設構造方法,只構造空頁
- */
- protected Page(){
- this.init(0,0,0,DEFAULT_PAGE_SIZE,new ());
- }
- /**
- * 分頁資料初始方法,由子類呼叫
- * @param start 本頁資料在資料庫中的起始位置
- * @param avaCount 本頁包含的資料條數
- * @param totalSize 資料庫中總記錄條數
- * @param pageSize 本頁容量
- * @param data 本頁包含的資料
- */
- protected void init(int start, int avaCount, int totalSize, int pageSize, data){
- this.avaCount =avaCount;
- this.myPageSize = pageSize;
- this.start = start;
- this.totalSize = totalSize;
- this.data=data;
- //System.out.println("avaCount:"+avaCount);
- //System.out.println("totalSize:"+totalSize);
- if (avaCount>totalSize) {
- //throw new RuntimeException("記錄條數大於總條數?!");
- }
- this.currentPageno = (start -1)/pageSize +1;
- this.totalPageCount = (totalSize + pageSize -1) / pageSize;
- if (totalSize==0 && avaCount==0){
- this.currentPageno = 1;
- this.totalPageCount = 1;
- }
- //System.out.println("Start Index to Page No: " + start + "-" + currentPageno);
- }
- public getData(){
- return this.data;
- }
- /**
- * 取本頁資料容量(本頁能包含的記錄數)
- * @return 本頁能包含的記錄數
- */
- public int getPageSize(){
- return this.myPageSize;
- }
- /**
- * 是否有下一頁
- * @return 是否有下一頁
- */
- public boolean hasNextPage() {
- /*
- if (avaCount==0 && totalSize==0){
- return false;
- }
- return (start + avaCount -1) < totalSize;
- */
- return (this.getCurrentPageNo()<this.getTotalPageCount());
- }
- /**
- * 是否有上一頁
- * @return 是否有上一頁
- */
- public boolean hasPreviousPage() {
- /*
- return start > 1;
- */
- return (this.getCurrentPageNo()>1);
- }
- /**
- * 獲取當前頁第一條資料在資料庫中的位置
- * @return
- */
- public int getStart(){
- return start;
- }
- /**
- * 獲取當前頁最後一條資料在資料庫中的位置
- * @return
- */
- public int getEnd(){
- int end = this.getStart() + this.getSize() -1;
- if (end<0) {
- end = 0;
- }
- return end;
- }
- /**
- * 獲取上一頁第一條資料在資料庫中的位置
- * @return 記錄對應的rownum
- */
- public int getStartOfPreviousPage() {
- return Math.max(start-myPageSize, 1);
- }
- /**
- * 獲取下一頁第一條資料在資料庫中的位置
- * @return 記錄對應的rownum
- */
- public int getStartOfNextPage() {
- return start + avaCount;
- }
- /**
- * 獲取任一頁第一條資料在資料庫中的位置,每頁條數使用預設值
- * @param pageNo 頁號
- * @return 記錄對應的rownum
- */
- public static int getStartOfAnyPage(int pageNo){
- return getStartOfAnyPage(pageNo, DEFAULT_PAGE_SIZE);
- }
- /**
- * 獲取任一頁第一條資料在資料庫中的位置
- * @param pageNo 頁號
- * @param pageSize 每頁包含的記錄數
- * @return 記錄對應的rownum
- */
- public static int getStartOfAnyPage(int pageNo, int pageSize){
- int startIndex = (pageNo-1) * pageSize + 1;
- if ( startIndex < 1) startIndex = 1;
- //System.out.println("Page No to Start Index: " + pageNo + "-" + startIndex);
- return startIndex;
- }
- /**
- * 取本頁包含的記錄數
- * @return 本頁包含的記錄數
- */
- public int getSize() {
- return avaCount;
- }
- /**
- * 取資料庫中包含的總記錄數
- * @return 資料庫中包含的總記錄數
- */
- public int getTotalSize() {
- return this.totalSize;
- }
- /**
- * 取當前頁碼
- * @return 當前頁碼
- */
- public int getCurrentPageNo(){
- return this.currentPageno;
- }
- /**
- * 取總頁碼
- * @return 總頁碼
- */
- public int getTotalPageCount(){
- return this.totalPageCount;
- }
- /**
- *
- * @param queryJSFunctionName 實現分頁的JS指令碼名字,頁碼變動時會自動回撥該方法
- * @param pageNoParamName 頁碼引數名稱
- * @return
- */
- public getHTML( queryJSFunctionName, pageNoParamName){
- if (getTotalPageCount()<1){
- return "<input type='hidden' name='"+pageNoParamName+"' value='1' >";
- }
- if (queryJSFunctionName == null || queryJSFunctionName.trim().length()<1) {
- queryJSFunctionName = "gotoPage";
- }
- if (pageNoParamName == null || pageNoParamName.trim().length()<1){
- pageNoParamName = "pageno";
- }
- gotoPage = "_"+queryJSFunctionName;
- html.append("<script language=/"Javascript1.2/">/n")
- .append("function ").append(gotoPage).append("(pageNo){ /n")
- .append( " var curPage=1; /n")
- .append( " try{ curPage = document.all[/"")
- .append(pageNoParamName).append("/"].value; /n")
- .append( " document.all[/"").append(pageNoParamName)
- .append("/"].value = pageNo; /n")
- .append( " ").append(queryJSFunctionName).append("(pageNo); /n")
- .append( " return true; /n")
- .append( " }catch(e){ /n")
- // .append( " try{ /n")
- // .append( " document.forms[0].submit(); /n")
- // .append( " }catch(e){ /n")
- .append( " alert('尚未定義查詢方法:function ")
- .append(queryJSFunctionName).append("()'); /n")
- .append( " document.all[/"").append(pageNoParamName)
- .append("/"].value = curPage; /n")
- .append( " return false; /n")
- // .append( " } /n")
- .append( " } /n")
- .append( "}")
- .append( "</script> /n")
- .append( "");
- html.append( "<table border=0 cellspacing=0 cellpadding=0 align=center width=80%> /n")
- .append( " <tr> /n")
- .append( " <td align=left><br> /n");
- html.append( " 共" ).append( getTotalPageCount() ).append( "頁")
- .append( " [") .append(getStart()).append("..").append(getEnd())
- .append("/").append(this.getTotalSize()).append("] /n")
- .append( " </td> /n")
- .append( " <td align=right> /n");
- if (hasPreviousPage()){
- html.append( "[<a href='javascript:").append(gotoPage)
- .append("(") .append(getCurrentPageNo()-1)
- .append( ")'>上一頁</a>] /n");
- }
- html.append( " 第")
- .append( " <select name='")
- .append(pageNoParamName).append("' onChange='javascript:")
- .append(gotoPage).append("(this.value)'>/n");
- selected = "selected";
- for(int i=1;i<=getTotalPageCount();i++){
- if( i == getCurrentPageNo() )
- selected = "selected";
- else selected = "";
- html.append( " <option value='").append(i).append("' ")
- .append(selected).append(">").append(i).append("</option> /n");
- }
- if (getCurrentPageNo()>getTotalPageCount()){
- html.append( " <option value='").append(getCurrentPageNo())
- .append("' selected>").append(getCurrentPageNo())
- .append("</option> /n");
- }
- html.append( " </select>頁 /n");
- if (hasNextPage()){
- html.append( " [<a href='javascript:").append(gotoPage)
- .append("(").append((getCurrentPageNo()+1))
- .append( ")'>下一頁</a>] /n");
- }
- html.append( "</td></tr></table> /n");
- return html.toString();
- }
- }
- ///////////////////////////////////
- //
- // RowSetPage.java
- // author: [email protected]
- //
- ///////////////////////////////////
- package page;
- import javax.sql.;
- /**
- * <p>Title: RowSetPage</p>
- * <p>Description: 使用RowSet封裝資料的分頁物件</p>
- * <p>Copyright: Copyright (c) 2003</p>
- * @author [email protected]
- * @version 1.0
- */
- public class RowSetPage extends Page {
- private javax.sql. rs;
- /**
- *空頁
- */
- public static final RowSetPage EMPTY_PAGE = new RowSetPage();
- /**
- *預設構造方法,建立空頁
- */
- public RowSetPage(){
- this(null, 0,0);
- }
- /**
- *構造分頁物件
- *@param crs 包含一頁資料的OracleCachedRowSet
- *@param start 該頁資料在資料庫中的起始位置
- *@param totalSize 資料庫中包含的記錄總數
- */
- public RowSetPage( crs, int start, int totalSize) {
- this(crs,start,totalSize,Page.DEFAULT_PAGE_SIZE);
- }
- /**
- *構造分頁物件
- *@param crs 包含一頁資料的OracleCachedRowSet
- *@param start 該頁資料在資料庫中的起始位置
- *@param totalSize 資料庫中包含的記錄總數
- *@pageSize 本頁能容納的記錄數
- */
- public RowSetPage( crs, int start, int totalSize, int pageSize) {
- try{
- int avaCount=0;
- if (crs!=null) {
- crs.beforeFirst();
- if (crs.next()){
- crs.last();
- avaCount = crs.getRow();
- }
- crs.beforeFirst();
- }
- rs = crs;
- super.init(start,avaCount,totalSize,pageSize,rs);
- }catch(java.sql. sqle){
- throw new (sqle.toString());
- }
- }
- /**
- *取分頁物件中的記錄資料
- */
- public javax.sql. getRowSet(){
- return rs;
- }
- }
- ///////////////////////////////////
- //
- // PagedStatement.java
- // author: [email protected]
- //
- ///////////////////////////////////
- package page;
- import foo.DBUtil;
- import java.math.;
- import java.util.List;
- import java.util.;
- import java.util.;
- import java.sql.;
- import java.sql.;
- import java.sql.;
- import java.sql.;
- import javax.sql.;
- /**
- * <p>Title: 分頁查詢</p>
- * <p>Description: 根據查詢語句和頁碼查詢出當頁資料</p>
- * <p>Copyright: Copyright (c) 2002</p>
- * @author [email protected]
- * @version 1.0
- */
- public abstract class PagedStatement {
- public final static int MAX_PAGE_SIZE = Page.MAX_PAGE_SIZE;
- protected countSQL, querySQL;
- protected int pageNo,pageSize,startIndex,totalCount;
- protected javax.sql. rowSet;
- protected RowSetPage rowSetPage;
- private List boundParams;
- /**
- * 構造一查詢出所有資料的PageStatement
- * @param sql query sql
- */
- public PagedStatement( sql){
- this(sql,1,MAX_PAGE_SIZE);
- }
- /**
- * 構造一查詢出當頁資料的PageStatement
- * @param sql query sql
- * @param pageNo 頁碼
- */
- public PagedStatement( sql, int pageNo){
- this(sql, pageNo, Page.DEFAULT_PAGE_SIZE);
- }
- /**
- * 構造一查詢出當頁資料的PageStatement,並指定每頁顯示記錄條數
- * @param sql query sql
- * @param pageNo 頁碼
- * @param pageSize 每頁容量
- */
- public PagedStatement( sql, int pageNo, int pageSize){
- this.pageNo = pageNo;
- this.pageSize = pageSize;
- this.startIndex = Page.getStartOfAnyPage(pageNo, pageSize);
- this.boundParams = .synchronizedList(new java.util.());
- this.countSQL = "select count(*) from ( " + sql +") ";
- this.querySQL = intiQuerySQL(sql, this.startIndex, pageSize);
- }
- /**
- *生成查詢一頁資料的sql語句
- *@param sql 原查詢語句
- *@startIndex 開始記錄位置
- *@size 需要獲取的記錄數
- */
- protected abstract intiQuerySQL( sql, int startIndex, int size);
- /**
- *使用給出的物件設定指定引數的值
- *@param index 第一個引數為1,第二個為2,。。。
- *@param obj 包含引數值的物件
- */
- public void setObject(int index, obj) throws {
- BoundParam bp = new BoundParam(index, obj);
- boundParams.remove(bp);
- boundParams.add( bp);
- }
- /**
- *使用給出的物件設定指定引數的值
- *@param index 第一個引數為1,第二個為2,。。。
- *@param obj 包含引數值的物件
- *@param targetSqlType 引數的資料庫型別
- */
- public void setObject(int index, obj, int targetSqlType) throws {
- BoundParam bp = new BoundParam(index, obj, targetSqlType);
- boundParams.remove(bp);
- boundParams.add(bp );
- }
- /**
- *使用給出的物件設定指定引數的值
- *@param index 第一個引數為1,第二個為2,。。。
- *@param obj 包含引數值的物件
- *@param targetSqlType 引數的資料庫型別(常量定義在java.sql.Types中)
- *@param scale 精度,小數點後的位數
- * (只對targetSqlType是Types.NUMBER或Types.DECIMAL有效,其它型別則忽略)
- */
- public void setObject(int index, obj, int targetSqlType, int scale) throws {
- BoundParam bp = new BoundParam(index, obj, targetSqlType, scale) ;
- boundParams.remove(bp);
- boundParams.add(bp);
- }
- /**
- *使用給出的字串設定指定引數的值
- *@param index 第一個引數為1,第二個為2,。。。
- *@param str 包含引數值的字串
- */
- public void setString(int index, str)throws {
- BoundParam bp = new BoundParam(index, str) ;
- boundParams.remove(bp);
- boundParams.add(bp);
- }
- /**
- *使用給出的字串設定指定引數的值
- *@param index 第一個引數為1,第二個為2,。。。
- *@param timestamp 包含引數值的時間戳
- */
- public void setTimestamp(int index, timestamp)throws {
- BoundParam bp = new BoundParam(index, timestamp) ;
- boundParams.remove(bp);
- boundParams.add( bp );
- }
- /**
- *使用給出的整數設定指定引數的值
- *@param index 第一個引數為1,第二個為2,。。。
- *@param value 包含引數值的整數
- */
- public void setInt(int index, int value)throws {
- BoundParam bp = new BoundParam(index, new (value)) ;
- boundParams.remove(bp);
- boundParams.add( bp );
- }
- /**
- *使用給出的長整數設定指定引數的值
- *@param index 第一個引數為1,第二個為2,。。。
- *@param value 包含引數值的長整數
- */
- public void setLong(int index, long value)throws {
- BoundParam bp = new BoundParam(index, new Long(value)) ;
- boundParams.remove(bp);
- boundParams.add( bp );
- }
- /**
- *使用給出的雙精度浮點數設定指定引數的值
- *@param index 第一個引數為1,第二個為2,。。。
- *@param value 包含引數值的雙精度浮點數
- */
- public void setDouble(int index, double value)throws {
- BoundParam bp = new BoundParam(index, new (value)) ;
- boundParams.remove(bp);
- boundParams.add( bp);
- }
- /**
- *使用給出的BigDecimal設定指定引數的值
- *@param index 第一個引數為1,第二個為2,。。。
- *@param bd 包含引數值的BigDecimal
- */
- public void setBigDecimal(int index, bd)throws {
- BoundParam bp = new BoundParam(index, bd ) ;
- boundParams.remove(bp);
- boundParams.add( bp);
- }
- if (pst==null || this.boundParams==null || this.boundParams.size()==0 ) return ;
- BoundParam param;
- for ( itr = this.boundParams.iterator();itr.hasNext();){
- param = (BoundParam) itr.next();
- if (param==null) continue;
- if (param.sqlType == java.sql.Types.OTHER){
- pst.setObject(param.index, param.value);
- }else{
- pst.setObject(param.index, param.value, param.sqlType, param.scale);
- }
- }
- }
- /**
- * 執行查詢取得一頁資料,執行結束後關閉資料庫連線
- * @return RowSetPage
- * @throws SQLException
- */
- public RowSetPage executeQuery() throws {
- .out.println("executeQueryUsingPreparedStatement");
- conn = DBUtil.getConnection();
- try{
- pst = conn.prepareStatement(this.countSQL);
- setParams(pst);
- rs =pst.executeQuery();
- if (rs.next()){
- totalCount = rs.getInt(1);
- } else {
- totalCount = 0;
- }
- rs.close();
- pst.close();
- if (totalCount < 1 ) return RowSetPage.EMPTY_PAGE;
- pst = conn.prepareStatement(this.querySQL);
- .out.println(querySQL);
- pst.setFetchSize(this.pageSize);
- setParams(pst);
- rs =pst.executeQuery();
- //rs.setFetchSize(pageSize);
- this.rowSet = populate(rs);
- rs.close();
- rs = null;
- pst.close();
- pst = null;
- this.rowSetPage = new RowSetPage(this.rowSet,startIndex,totalCount,pageSize);
- return this.rowSetPage;
- //System.out.println("executeQuery SQLException");
- sqle.printStackTrace();
- throw sqle;
- e.printStackTrace();
- }finally{
- //System.out.println("executeQuery finally");
- DBUtil.close(rs, pst, conn);
- }
- }
- /**
- *將ResultSet資料填充進CachedRowSet
- */
- /**
- *取封裝成RowSet查詢結果
- *@return RowSet
- */
- public javax.sql. getRowSet(){
- return this.rowSet;
- }
- /**
- *取封裝成RowSetPage的查詢結果
- *@return RowSetPage
- */
- public RowSetPage getRowSetPage() {
- return this.rowSetPage;
- }
- /**
- *關閉資料庫連線
- */
- public void close(){
- //因為資料庫連線在查詢結束或發生異常時即關閉,此處不做任何事情
- //留待擴充。
- }
- private class BoundParam {
- int index;
- int sqlType;
- int scale;
- public BoundParam(int index, value) {
- this(index, value, java.sql.Types.OTHER);
- }
- public BoundParam(int index, value, int sqlType) {
- this(index, value, sqlType, 0);
- }
- public BoundParam(int index, value, int sqlType, int scale) {