在使用數據庫的過程中,不可避免地要使用分頁功能,但是JDBC規範並沒有很好地解決這個問題。對於這種需求,很多朋友都有自己的解決方案,比如使用Vector之類的集合類在分頁之前保存取來的數據。但是這種方法的可用性很差,和JDBC自己的界面完全不壹樣,對不同類型的字段支持也很差。這裏有壹個與JDBC兼容的非常好的解決方案。
JDBC和尋呼
孫的規範的提法有時讓人哭笑不得。在JDBC1.0中,只能對壹個ResultSet執行next()操作,而不能使其向後滾動,直接導致只執行壹次SQL查詢就無法獲得結果集的大小。所以如果使用JDBC1.0驅動,幾乎不可能實現分頁。
好在Sun的JDBC2規範很好的彌補了這個不足,增加了ResultSet的前後滾動操作。雖然我們仍然不能直接支持分頁,但我們已經可以在此基礎上編寫自己的支持分頁的ResultSet。
與特定數據庫相關的實現方法
有些數據庫,比如Mysql,Oracle,都有自己的分頁方法。例如,Mysql可以使用limit子句,Oracle可以使用ROWNUM來限制結果集的大小和起始位置。這裏以Mysql為例,其典型代碼如下:
//計算記錄總數。
string SQL = " SELECT Count(*)AS total "+this。QueryPart
RS = db . execute query(SQL);
if (rs.next())
total = RS . getint(1);
//設置當前頁碼和總頁碼。
t pages =(int)math . ceil((double)this。總計/本。MaxLine);
c pages =(int)math . floor((double)Offset/this。MaxLine+1);
//根據條件,取出需要的記錄。
if(總計& gt0) {
SQL = Query + " LIMIT " + Offset +","+MaxLine;
RS = db . execute query(SQL);
}
返回RS;
}
毫無疑問,這個代碼在數據庫是Mysql的情況下會很漂亮,但是作為壹個通用類(其實我後面提供的是壹個通用類庫的壹部分),它需要適應不同的數據庫,基於這個類(庫)的應用也可能使用不同的數據庫,所以我們不會用這個方法。
另壹個繁瑣的實現方法
我見過有人這麽做的(其實包括我在內,壹開始也是用這種方法),就是不使用任何封裝,直接操作ResultSet滾動到需要分頁的對應位置,然後讀取對應的記錄數。其典型代碼如下:
& lt%
sqlStmt = sqlcon . create statement(Java . SQL . resultset . type _ SCROLL _ INSENSITIVE,
Java . SQL . resultset . concur _ READ _ ONLY);
strSQL = "從測試中選擇姓名、年齡";
//執行SQL語句,得到結果集。
sqlRst = sqlstmt . execute query(strSQL);
//獲取記錄總數
sqlrst . last();
intRowCount = SQL rst . getrow();
//統計總頁數
int pagecount =(intRowCount+int pagesize-1)/int pagesize;
//調整要顯示的頁碼
if(int page & gt;int page count)int page = int page count;
% & gt
& lttable border = " 1 " cellspacing = " 0 " cell padding = " 0 " >
& lttr & gt
& ltth & gt名字
& ltth & gt年齡
& lt/tr & gt;
& lt%
if(int page count & gt;0){
//將記錄指針定位在要顯示的頁面的第壹條記錄上。
sqlrst . absolute((int page-1)* int pagesize+1);
//顯示數據
I = 0;
而(我& ltintPageSize & amp& amp!sqlRst.isAfterLast()){
% & gt
& lttr & gt
& lttd & gt& lt% = SQL rst . getstring(1)% & gt;& lt/TD & gt;
& lttd & gt& lt% = SQL rst . getstring(2)% & gt;& lt/TD & gt;
& lt/tr & gt;
& lt%
sqlrst . next();
i++;
}
}
% & gt
& lt/table & gt;
顯然,這種方法沒有考慮到代碼重用的問題。不僅代碼量巨大,在需要修改代碼的時候也會手足無措。
使用向量分頁
我也見過其他實現分頁的類,就是先選擇所有記錄,然後獲取ResultSet中的所有數據,存儲在Vector等集合類中,再根據需要的分頁大小和頁數將數據定位到相應的位置。或者使用上面提到的兩種分頁方法,在Vector中存儲之前獲取所需的頁面。
拋開代碼的效率不說,從程序結構和使用的便利性來說都是非常糟糕的。比如這種方法支持的字段類型有限,int、double、String類型相對容易處理。如果遇到Blob、Text之類的類型,實現起來會很麻煩。這是壹個更不可取的方案。
壹種新的可分頁接口及其實現
顯然,看了以上三種實現方法,我們對於新的分頁機制有了壹個目標,那就是與具體的數據庫無關;盡可能復用代碼;盡可能與原JDBC界面保持壹致;盡可能高效。
首先我們需要提供壹個java.sql.ResultSet向後兼容的接口,命名為Pageable。該接口定義如下:
公共接口Pageable擴展java.sql.ResultSet{
/* *返回總頁數。
*/
int getpage count();
/* *返回當前頁面中的記錄數。
*/
int getPageRowsCount();
/* *返回頁面大小。
*/
int get pagesize();
/* *轉到指定頁面
*/
void gotoPage(int page);
/* *設置分頁大小
*/
void setPageSize(int pageSize);
/* *返回記錄行的總數。
*/
int getRowsCount();
/**
*轉到當前頁面的第壹條記錄。
* @ exception Java . SQL . SQL exception異常描述。
*/
void pageFirst()拋出Java . SQL . SQL exception;
/**
*轉到當前頁面的最後壹條記錄。
* @ exception Java . SQL . SQL exception異常描述。
*/
void pageLast()拋出Java . SQL . SQL exception;
/* *返回當前頁碼。
*/
int getCurPage();
}
這是壹個擴展java.sql.ResultSet的接口,主要是增加了對分頁的支持,比如設置分頁大小,跳轉到壹個頁面,返回總頁數等等。
然後,我們需要實現這個接口,因為這個接口繼承自ResultSet,它的大部分函數與ResultSet的原始函數相同,所以這裏使用了壹個簡單的Decorator模式。
PageableResultSet2的類聲明和成員聲明如下:
公共類PageableResultSet2實現Pageable {
受保護的Java . SQL . resultset RS = null;
受保護的int rowsCount
受保護的int pageSize
受保護的int curPage
受保護的字符串命令= " ";
}
可以看到PageableResultSet2包含了ResultSet的壹個實例(這個實例只實現了ResultSet接口,實際上是各種數據庫廠商實現的),所有從ResultSet繼承的方法都直接轉發到這個實例進行處理。
PageableResultSet2中從ResultSet繼承的主要方法:
//……
public boolean next()拋出SQLException {
返回RS . next();
}
//……
公共字符串getString(String columnName)引發SQLException {
嘗試{
返回RS . getstring(column name);
}
Catch (SQLException e) {//這是為了調試增加壹些錯誤信息。
拋出新的SQLException(e . tostring()+" column name = "
+column name+" SQL = "+this . get command());
}
}
//……
只有可分頁接口中新添加的方法才需要用自己的編寫方法來處理。
/* *有關方法說明,請參考Pageable.java。
*/
public int getCurPage() {
返回curPage
}
public int getPageCount() {
if(rowsCount==0)返回0;
if(pageSize==0)返回1;
//計算頁面計數
double tmpD =(double)rows count/pageSize;
int tmpI =(int)tmpD;
if(tmpD & gt;tmpI)tmpi++;
返回tmpI
}
public int getPageRowsCount() {
if(pageSize==0)返回rowsCount
if(getRowsCount()==0)返回0;
如果(curPage!=getPageCount())返回pageSize
return rows count-(getPageCount()-1)* pageSize;
}
public int getPageSize() {
返回pageSize
}
public int getRowsCount() {
返回rowsCount
}
public void gotoPage(int page) {
if (rs == null)
返回;
如果(page & lt1)
page = 1;
如果(第頁& gtgetPageCount())
page = getpage count();
int row =(page-1)* pageSize+1;
嘗試{
RS . absolute(row);
curPage = page
}
catch (java.sql.SQLException e) {
}
}
public void pageFirst()拋出java.sql.SQLException {
int row =(cur page-1)* pageSize+1;
RS . absolute(row);
}
public void pageLast()拋出java.sql.SQLException {
int row =(cur page-1)* pageSize+getPageRowsCount();
RS . absolute(row);
}
public void setPageSize(int pageSize){
if(pageSize & gt;=0){
this.pageSize = pageSize
cur page = 1;
}
}
PageableResultSet2的構造方法:
public pageable resultset 2(Java . SQL . resultset RS)拋出java.sql.SQLException {
if(rs==null)拋出新的SQLException("給定結果集為null ","用戶");
RS . last();
rows count = RS . getrow();
RS . before first();
this.rs = rs
}
在這裏,我們簡單地獲取記錄總數,將記錄光標移回原始位置(在first之前),並將參數中的結果集賦給成員變量。
如何使用可分頁
由於Pageable接口繼承自ResultSet,所以在用法上與ResultSet壹致,尤其是不需要分頁函數時,可以直接作為ResultSet使用。需要分頁的時候,只需要壹個簡單的setPageSize,gotoPage。
PreparedStatement pstmt = null
可分頁rs = null
.....//構造SQL,準備壹個pstmt。
RS = new pageableresultset 2(pstmt . execute query());//構造可分頁的
RS . setpagesize(20);//每頁20條記錄
RS . goto page②;//跳轉到第2頁
for(int I = 0;我& ltRS . getpagerowscount();I++){//循環處理
int ID = RS . getint(" ID ");
.....//繼續處理
}
摘要
壹個好的基礎類應該是易用的,有足夠的可移植性,同時保證功能的完善。在上面的實現中,我們從java.sql.ResultSet接口繼承了Pageable並實現了它。這樣既保證了使用中與JDBC原有操作的壹致性,同時又不降低原有功能。
同時,它很容易使用,因為它封裝了所有必要的操作,所以在妳的代碼中唯壹顯得“醜陋”和“不舒服”的是妳需要自己構造壹個PageableResultSet2。但是如果妳想的話是可以解決的。
當然,它也是完全便攜的。當您將數據庫從Oracle更改為Mysql或SQLServer時,仍然可以使用這些分頁代碼。它在使用中(或者在移植過程中)唯壹的限制就是妳必須使用壹個支持JDBC2的驅動程序(現在看看我為什麽把這個類命名為PageableResultSet2)。:p),不過好在JDBC2已經成為標準,大部分數據庫(如Oracle、Mysql、SQLServer)都有自己的JDBC2驅動或者第三方提供的驅動。
好了,這個分頁的實現對妳的編程有幫助嗎?仔細壹看,其實自己寫的代碼並不多,大部分只是簡單的轉發操作。壹個合適的模式應用可以幫到妳很多。