前言 "xe7Dl
在使用数据库的过程中,不可避免的需要使用到分页的功能,可是JDBC的规范对此却没有很好的解决。对于这个需求很多朋友都有自己的解决方案,比如使用Vector等集合类先保存取出的数据再分页。但这种方法的可用性很差,与JDBC本身的接口完全不同,对不同类型的字段的支持也不好。这里提供了一种与JDBC兼容性非常好的方案。 ]97Xu_
JDBC和分页 q5:0&:m$4$
Sun的JDBC规范的制定,有时很让人哭笑不得,在JDBC1.0中,对于一个结果集(ResultSet)你甚至只能执行next()操作,而无法让其向后滚动,这就直接导致在只执行一次SQL查询的情况下无法获得结果集的大小。所以,如果你使用的是JDBC1.0的驱动,那么是几乎无法实现分页的。 ESC
好在Sun的JDBC2规范中很好的弥补了这一个不足,增加了结果集的前后滚动操作,虽然仍然不能直接支持分页,但我们已经可以在这个基础上写出自己的可支持分页的ResultSet了。 $?]@_=
=Q/w% 8G
&57qjA,8<
.x)>f
和具体数据库相关的实现方法 O_r^oH
有一些数据库,如Mysql, Oracle等有自己的分页方法,比如Mysql可以使用limit子句,Oracle可以使用ROWNUM来限制结果集的大小和起始位置。这里以Mysql为例,其典型代码如下: $:%*gY4~76
// 计算总的记录条数 [ E$$nNs
String SQL = SELECT Count(*) AS total + this.QueryPart; ' +)6#/*
rs = db.executeQuery(SQL); SKB@
if (rs.next()) _/8_,9H
Total = rs.getInt(1); R7E]*:0}
// 设置当前页数和总页数 #W>x\
TPages = (int)Math.ceil((double)this.Total/this.MaxLine); hrJ$%U
CPages = (int)Math.floor((double)Offset/this.MaxLine+1); X)FL[RO%q
// 根据条件判断,取出所需记录 iyA*JCD
if (Total > 0) { w;N{>)hv
SQL = Query + LIMIT + Offset + , + MaxLine; Kwy1SyU
rs = db.executeQuery(SQL); a6;gBoV
} i:l80 GK
return rs; + 6x"trC
} z7'n, [
毫无疑问,这段代码在数据库是Mysql时将会是漂亮的,但是作为一个通用的类(事实上我后面要提供的就是一个通用类库中的一部分),需要适应不同的数据库,而基于这个类(库)的应用,也可能使用不同的数据库,所以,我们将不使用这种方法。 sl~b\j
|x1$b7
06PhrPVa!\
另一种繁琐的实现方法 y,'FTP9?
我看过一些人的做法(事实上包括我在内,一开始也是使用这种方法的),即不使用任何封装,在需要分页的地方,直接操作ResultSet滚到相应的位置,再读取相应数量的记录。其典型代码如下: Y
h^WTysBn
<% %3]3r*e&5
sqlStmt = sqlCon.createStatement(java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE, TZ^{pvBy
java.sql.ResultSet.CONCUR_READ_ONLY); Ix( 6
strSQL = select name,age from test; tH-gaDj_
//执行SQL语句并获取结果集 .(`(chRa}
sqlRst = sqlStmt.executeQuery(strSQL); PfVEv *
//获取记录总数 Y Eg
.
sqlRst.last(); l9]o\JFXk
intRowCount = sqlRst.getRow(); <$"7~i/X
//记算总页数 5/Ydv
RB67
intPageCount = (intRowCount+intPageSize-1) / intPageSize; FpP\-+Sl
//调整待显示的页码 z
VnIr<!8_
if(intPage>intPageCount) intPage = intPageCount; ?+3R^%`V
%> 6o$Z0mG
<table border=1 cellspacing=0 cellpadding=0> \V._Z>]
<tr> 'g. :MQ8
<th>姓名</th> 0Wa#lkn$I
<th>年龄</th> ri_P;#lz
</tr> lTn;3'
<% _Mlhumt
if(intPageCount>0){ RI?NB6U
//将记录指针定位到待显示页的第一条记录上 1UC2zM"
sqlRst.absolute((intPage-1) * intPageSize + 1); t$aVe"uM
//显示数据 D 5=C^`$2
i = 0; #X4LLS]VV
while(i<intPageSize && !sqlRst.isAfterLast()){ [0K=I64
z
%> )m|C8[ u
<tr> ;F|jG}M"
<td><%=sqlRst.getString(1)%></td> bSQ_"
<td><%=sqlRst.getString(2)%></td> h!QjpzQe
</tr> X88F>1}
<% )sqaR^
sqlRst.next(); `$H7KI G
i++; bC6X?m=
} g .3f2w
}
1U
%> Pv#KmSA9
</table> MdLj,1_T
很显然,这种方法没有考虑到代码重用的问题,不仅代码数量巨大,而且在代码需要修改的情况下,将会无所适从。 O)l%OOv
/;+oz
V!mWn|lf
使用Vector进行分页 sb'lZFSP~s
还见过另一些实现分页的类,是先将所有记录都select出来,然后将ResultSet中的数据都get出来,存入Vector等集合类中,再根据所需分页的大小,页数,定位到相应的位置,读取数据。或者先使用前面提到的两种分页方法,取得所需的页面之后,再存入Vector中。 ohtT
O]\
扔开代码的效率不说,单是从程序结构和使用的方便性上讲,就是很糟糕的。比如,这种做法支持的字段类型有限,int, double, String类型还比较好处理,如果碰到Blob, Text等类型,实现起来就很麻烦了。这是一种更不可取的方案。 E{<?l 7t
?b5H
2W
u]R$]&<
一个新的Pageable接口及其实现 L+Eu
d
很显然,看过上面三种实现方法后,我们对新的分页机制有了一个目标,即:不与具体数据库相关;尽可能做到代码重用;尽可能与原JDBC接口的使用方法保持一致;尽可能高的效率。 t`uc3ta"9
首先,我们需要提供一个与java.sql.ResultSet向下兼容的接口,把它命名为Pageable,接口定义如下: <8$Md4r
public interface Pageable extends java.sql.ResultSet{ Kf|0*c
/**返回总页数 av)?>J~;
*/ ^mAJ[^%
int getPageCount(); So?m?,!W
/**返回当前页的记录条数 B+|IZoR
*/ }N@n{bu+
int getPageRowsCount(); 9g96 d-
/**返回分页大小 $]Jf0_
*/ j+nv=p
int getPageSize(); f:S}h-AL&
/**转到指定页 ekrBNDs9
*/ !W(`<d]68:
void gotoPage(int page) ; KLW&bJ$|j
/**设置分页大小 gbJG`zC>U
*/ ;e2Ij
void setPageSize(int pageSize); Y4`QK+~fH
/**返回总记录行数 'g2vX&=$A
*/ `6UtxJSx
int getRowsCount(); |ebvx?\
/** IOX:yxj
* 转到当前页的第一条记录 >C:If0S4X
* @exception java.sql.SQLException 异常说明。 Q]^Yi1PbS
*/ u"\=^F
void pageFirst() throws java.sql.SQLException; uQWp+}>ZJy
/** G#|Hu;C6"
* 转到当前页的最后一条记录 +3)[>{~1Z
* @exception java.sql.SQLException 异常说明。 2?*||c==*
*/ (ZS/@He
void pageLast() throws java.sql.SQLException; ,!hnm
/**返回当前页号 UUGe"]V^g:
*/ &lU Ny
L
int getCurPage(); 6A.P6DW
} ~oo'ky*H!
这是一个对java.sql.ResultSet进行了扩展的接口,主要是增加了对分页的支持,如设置分页大小,跳转到某一页,返回总页数等等。 ZOC#i i`:
接着,我们需要实现这个接口,由于这个接口继承自ResultSet,并且它的大部分功能也都和ResultSet原有功能相同,所以这里使用了一个简单的Decorator模式。 ZzT&$J7]`{
PageableResultSet2的类声明和成员声明如下: ",T`\8&@e
public class PageableResultSet2 implements Pageable { wW^Zb
protected java.sql.ResultSet rs=null; 'd+:D'
protected int rowsCount; TH YVT%v
protected int pageSize; V'C-'Ythwf
protected int curPage; VE*j*U
j
protected String command = ; <$Z tik1
} qv$!\ T
可以看到,在PageableResultSet2中,包含了一个ResultSet的实例(这个实例只是实现了ResultSet接口,事实上它是由各个数据库厂商分别实现的),并且把所有由ResultSet继承来的方法都直接转发给该实例来处理。 _=?2 3
PageableResultSet2中继承自ResultSet的主要方法: r,Ds[s)B
//…… lJUy;yp_+
public boolean next() throws SQLException { #3.\j"b
return rs.next(); Oi6Eo~\f
} /7x\;&bc
//…… %EWq2'/5
public String getString(String columnName) throws SQLException { GyuV
%
try { -<PC"B
return rs.getString(columnName); N2[jO+6
} EavX8r
catch (SQLException e) {//这里是为了增加一些出错信息的内容便于调试 aiJnfU]W
throw new SQLException (e.toString()+ columnName= o)p[
C
+columnName+ SQL=+this.getCommand()); 0 7\02f
} Co[n--@C
} ` zY!`G
//…… L_k9g12
只有在Pageable接口中新增的方法才需要自己的写方法处理。 HA1]M`&
/**方法注释可参考Pageable.java 8""mp]o9
*/ $o"g73`3
public int getCurPage() { ol<lCp
return curPage; nPOO3!<{
} v&r=-}z2!
public int getPageCount() { St2Q7K5s{
if(rowsCount==0) return 0; M%|f+u &
if(pageSize==0) return 1; VAs(.y
//calculate PageCount Yg&`
U^7]B
double tmpD=(double)rowsCount/pageSize; <wa(xDBw
int tmpI=(int)tmpD; c|Y!c!9F
if(tmpD>tmpI) tmpI++; V_f`0\[x
return tmpI; 9x[ U$B
} CL1
oAk
public int getPageRowsCount() { l2s{~ IC
if(pageSize==0) return rowsCount; r:NH6tAL
if(getRowsCount()==0) return 0; U!0E_J
if(curPage!=getPageCount()) return pageSize; Hd374U<8]T
return rowsCount-(getPageCount()-1)*pageSize; O_AGMW/2+
} ]-a{IWVN
public int getPageSize() { {xToz]YA
return pageSize; EHhd;,;O
} k}U
JVH21k
public int getRowsCount() { `iQqhx
return rowsCount; 0bSz4<}
} 7k~Lttuk
public void gotoPage(int page) { N Bz%(?\
if (rs == null) :".w{0l@
return; ?+\E3}:
if (page < 1) fv2=B)8$
page = 1; B9^R8|V
if (page > getPageCount()) Egf^H>,.M
page = getPageCount(); 6c &Y
int row = (page - 1) * pageSize + 1; *KJ7nRKx(w
try { J=9 #mOcg"
rs.absolute(row); AerFgQiS
curPage = page; dhtb?n{
} ]Uh1l.O
catch (java.sql.SQLException e) { H`el#tt_
} oCuV9dA.
} M _ (2sq
public void pageFirst() throws java.sql.SQLException { !.-.#<<_a
int row=(curPage-1)*pageSize+1; GO~k '
rs.absolute(row); Q1T@oxV
} A?,A(-0C
public void pageLast() throws java.sql.SQLException { uLK(F
B
int row=(curPage-1)*pageSize+getPageRowsCount(); rN#\AN
rs.absolute(row); Q7(eq0na
} Y&GuDLUF
public void setPageSize(int pageSize) {
W_}/ O'l{
if(pageSize>=0){ l#xw.2bo
this.pageSize=pageSize; 0Cq!\nzz
curPage=1; $"fzBM?5
} LS6ry,D"7
} pBU]=[M0
PageableResultSet2的构造方法: }NwN2xTB
public PageableResultSet2(java.sql.ResultSet rs) throws java.sql.SQLException { |^iA6)Q
if(rs==null) throw new SQLException(given ResultSet is NULL,user); x8/us
3]`mQm E
rs.last(); 8K^f:)Qw
rowsCount=rs.getRow(); 2kJ!E@n7
rs.beforeFirst(); }p{;^B
#HG&[Ywi
this.rs=rs; 1R^4C8*B
} 1Z 6SI>p
这里只是简单的取得一个总记录数,并将记录游标移回初始位置(before first),同时将参数中的ResultSet赋给成员变量。 o(Kcs-W2
&^+3errO
>[Rz
<yv
Pageable的使用方法 9wMEvX70
因为Pageable接口继承自ResultSet,所以在使用方法上与ResultSet一致,尤其是在不需要分页功能的时候,可以直接当成ResultSet使用。而在需要分页时,只需要简单的setPageSize, gotoPage,即可。 >a@>N
PreparedStatement pstmt=null; [#Fg\2bq_y
Pageable rs=null; \ 3G*j`
……//构造SQL,并准备一个pstmt. NjP ]My
rs=new PageableResultSet2(pstmt.executeQuery());//构造一个Pageable F.0d4:A+
rs.setPageSize(20);//每页20个记录 7m]t^^
rs.gotoPage(2);//跳转到第2页 .YC;zn^
for(int i=0; i<rs.getPageRowsCount(); i++){//循环处理 k*Pz&8|
int id=rs.getInt(“ID”); 7E\gxQ(vU
……//继续处理 kwF] TO
S
} !0Nf9
Snx<