前言 Beg5[4@
在使用数据库的过程中,不可避免的需要使用到分页的功能,可是JDBC的规范对此却没有很好的解决。对于这个需求很多朋友都有自己的解决方案,比如使用Vector等集合类先保存取出的数据再分页。但这种方法的可用性很差,与JDBC本身的接口完全不同,对不同类型的字段的支持也不好。这里提供了一种与JDBC兼容性非常好的方案。 Kf~+jYobO
JDBC和分页 {E|gV9g
Sun的JDBC规范的制定,有时很让人哭笑不得,在JDBC1.0中,对于一个结果集(ResultSet)你甚至只能执行next()操作,而无法让其向后滚动,这就直接导致在只执行一次SQL查询的情况下无法获得结果集的大小。所以,如果你使用的是JDBC1.0的驱动,那么是几乎无法实现分页的。 +~O{
UGB=
好在Sun的JDBC2规范中很好的弥补了这一个不足,增加了结果集的前后滚动操作,虽然仍然不能直接支持分页,但我们已经可以在这个基础上写出自己的可支持分页的ResultSet了。 LP /4e`
fM.|#eLi
k^jCB>b
s#ZH.z@J
和具体数据库相关的实现方法 P.DWC'IBN
有一些数据库,如Mysql, Oracle等有自己的分页方法,比如Mysql可以使用limit子句,Oracle可以使用ROWNUM来限制结果集的大小和起始位置。这里以Mysql为例,其典型代码如下: ?F{xDfqw
// 计算总的记录条数 'O9=*L)X
String SQL = SELECT Count(*) AS total + this.QueryPart; {m:R v&T
rs = db.executeQuery(SQL); W^Y0>W~
if (rs.next()) ;bE6Y]"Rz
Total = rs.getInt(1); 3~rc=e
// 设置当前页数和总页数 cU|jT8Q4H
TPages = (int)Math.ceil((double)this.Total/this.MaxLine); Hc|U@G
CPages = (int)Math.floor((double)Offset/this.MaxLine+1); *pp1Wa7O
// 根据条件判断,取出所需记录 DU8LU*q'
if (Total > 0) { S
'+"+%^tj
SQL = Query + LIMIT + Offset + , + MaxLine; k1zt|
rs = db.executeQuery(SQL); U{(07GNm#
} aS G2K0
return rs; 7+4"+CA
} 8ZfIh
毫无疑问,这段代码在数据库是Mysql时将会是漂亮的,但是作为一个通用的类(事实上我后面要提供的就是一个通用类库中的一部分),需要适应不同的数据库,而基于这个类(库)的应用,也可能使用不同的数据库,所以,我们将不使用这种方法。 ^MV%\0o
c F]3gM
=lQ[%&
另一种繁琐的实现方法 H%aLkV!J
我看过一些人的做法(事实上包括我在内,一开始也是使用这种方法的),即不使用任何封装,在需要分页的地方,直接操作ResultSet滚到相应的位置,再读取相应数量的记录。其典型代码如下: ;(6lN<iU
<% |3ETF|)?
sqlStmt = sqlCon.createStatement(java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE, DjvgKy=Jr_
java.sql.ResultSet.CONCUR_READ_ONLY); B)8Hj).@B
strSQL = select name,age from test; y/eX(l<{
//执行SQL语句并获取结果集 Un{ln*AR\
sqlRst = sqlStmt.executeQuery(strSQL); 1s[-2^D+EM
//获取记录总数 uF"`y&go
sqlRst.last(); !Jl0Eu
intRowCount = sqlRst.getRow(); tC-KW~&
//记算总页数 [HDO^6U
intPageCount = (intRowCount+intPageSize-1) / intPageSize; %tQ{Hf~
//调整待显示的页码 >+8I =S
if(intPage>intPageCount) intPage = intPageCount; ~1sl.8tF
%> A"iD4Q
<table border=1 cellspacing=0 cellpadding=0> Q@VnJ,
<tr> u6T?oK9j
<th>姓名</th> >irT|VTf
<th>年龄</th> :/%xK"
</tr> !5!$h`g
<% rxeXz<
if(intPageCount>0){ Nn1^#kc
//将记录指针定位到待显示页的第一条记录上 RGI6W{\
sqlRst.absolute((intPage-1) * intPageSize + 1); @A'1D@f#
//显示数据 e/jM+%
i = 0; Gi4dgMVei
while(i<intPageSize && !sqlRst.isAfterLast()){ Wb4{*~
%> Qp&ySU8
<tr>
w{EU9C
<td><%=sqlRst.getString(1)%></td> 15DK\_;
<td><%=sqlRst.getString(2)%></td> Hd`p_?3]
</tr> CT%m_lN
<% ^|(4j_.(e
sqlRst.next(); }/3pC a
i++; "m;]6B."
} z}&C(m:al
} BM~niW;k
%> ){6)?[G
</table> UVUO}B@[S
很显然,这种方法没有考虑到代码重用的问题,不仅代码数量巨大,而且在代码需要修改的情况下,将会无所适从。 i9U_r._qj;
G<6grd5PP
LlY*r+Cgl1
使用Vector进行分页 }(EOQ2TI
还见过另一些实现分页的类,是先将所有记录都select出来,然后将ResultSet中的数据都get出来,存入Vector等集合类中,再根据所需分页的大小,页数,定位到相应的位置,读取数据。或者先使用前面提到的两种分页方法,取得所需的页面之后,再存入Vector中。 /C2f;h(1
扔开代码的效率不说,单是从程序结构和使用的方便性上讲,就是很糟糕的。比如,这种做法支持的字段类型有限,int, double, String类型还比较好处理,如果碰到Blob, Text等类型,实现起来就很麻烦了。这是一种更不可取的方案。 WTs[Sud/
UDtbfc7bk
4,ynt&
一个新的Pageable接口及其实现 Ltd?#HP
很显然,看过上面三种实现方法后,我们对新的分页机制有了一个目标,即:不与具体数据库相关;尽可能做到代码重用;尽可能与原JDBC接口的使用方法保持一致;尽可能高的效率。 F>(#Af9
首先,我们需要提供一个与java.sql.ResultSet向下兼容的接口,把它命名为Pageable,接口定义如下: wD^do
public interface Pageable extends java.sql.ResultSet{ \[I .
/**返回总页数 $=xQ X
*/ b7sE
int getPageCount(); m>dcb
6B+g
/**返回当前页的记录条数 y]f^`2L!8>
*/ lA-!~SM v"
int getPageRowsCount(); f,inQ2f}d
/**返回分页大小 [Fj+p4*N
*/ M8j(1&(:
int getPageSize(); &ntP~!w
/**转到指定页 13_~)V
*/ ;Jn0e:x`E
void gotoPage(int page) ; slvs oN@
/**设置分页大小 e -]c
*/ Cf=q_\0|W
void setPageSize(int pageSize); TM}'XZ&
/**返回总记录行数 P`IG9
*/ (,c?}TP
int getRowsCount(); M2P@ &
/** 33*d/%N9
* 转到当前页的第一条记录 ,xD*^>!
* @exception java.sql.SQLException 异常说明。 x$J.SbW
*/ *@n3>$
void pageFirst() throws java.sql.SQLException; |$?Ux,(6
/** \(U" _NPp
* 转到当前页的最后一条记录 vcJb\LW
* @exception java.sql.SQLException 异常说明。 R:BBNzY}f
*/ nk|N.%E
void pageLast() throws java.sql.SQLException; GKujDx+h
/**返回当前页号 jl-Aos"/
*/ ^@*zH?Rx{
int getCurPage(); n!eqzr{
} p6y0W`U
这是一个对java.sql.ResultSet进行了扩展的接口,主要是增加了对分页的支持,如设置分页大小,跳转到某一页,返回总页数等等。 qTh='~m4[
接着,我们需要实现这个接口,由于这个接口继承自ResultSet,并且它的大部分功能也都和ResultSet原有功能相同,所以这里使用了一个简单的Decorator模式。 ka)LK@p6
PageableResultSet2的类声明和成员声明如下: ^ lc}FN
public class PageableResultSet2 implements Pageable { :`u&TXsu
protected java.sql.ResultSet rs=null; M:UB>-`bW
protected int rowsCount; m|2]lb
protected int pageSize; $<
K)fbG
protected int curPage; P[GX}~_k
protected String command = ; G1;'nwf}
} )cqDvH
可以看到,在PageableResultSet2中,包含了一个ResultSet的实例(这个实例只是实现了ResultSet接口,事实上它是由各个数据库厂商分别实现的),并且把所有由ResultSet继承来的方法都直接转发给该实例来处理。 OV("mNh
PageableResultSet2中继承自ResultSet的主要方法: 6SBvn%
//…… p@7i=hyt`p
public boolean next() throws SQLException { ;.Oh88|k
return rs.next(); Xtu`5p_Qv
} mn; 7o~4
//…… DkF2R @
public String getString(String columnName) throws SQLException { `KJYm|@ i
try { {[t"O u
return rs.getString(columnName); Z~phOv
} l^UJes!
catch (SQLException e) {//这里是为了增加一些出错信息的内容便于调试 7?!Z+r
throw new SQLException (e.toString()+ columnName= j*La,iF
+columnName+ SQL=+this.getCommand()); %][$y7
} [X">vaa
} Op/79]$
//…… j
#I:6yA3
只有在Pageable接口中新增的方法才需要自己的写方法处理。 <A -(&+
/**方法注释可参考Pageable.java O? Gl4_y
*/ <[y$D=n
public int getCurPage() { H
MjeGO.i
return curPage; yg+IkQDf4U
} 0gOrW=
public int getPageCount() { "?eH=!
if(rowsCount==0) return 0; cR=94i=t
if(pageSize==0) return 1; TcKvSdr'
//calculate PageCount g#'fd/?Q
double tmpD=(double)rowsCount/pageSize; x*R8^BA]pR
int tmpI=(int)tmpD; UrhM)h?%
if(tmpD>tmpI) tmpI++; L;--d`[
return tmpI; TI[UX16Tz1
} U%^eIXV|
public int getPageRowsCount() { q1TW?\pjb:
if(pageSize==0) return rowsCount; P"bknXL
if(getRowsCount()==0) return 0; .mT#%ex
if(curPage!=getPageCount()) return pageSize; txml*/zL
return rowsCount-(getPageCount()-1)*pageSize; \>Ga-gv6/
} /K,|k
EE'n
public int getPageSize() { s!hI:$J.
return pageSize; lLkmcHu
} 'Uko^R)(
public int getRowsCount() { T}t E/
return rowsCount; eg2U+g4
} +=6RmId+X
public void gotoPage(int page) { 4z9#M;qT
if (rs == null) c:llOHA
return; =CjNtD2]
if (page < 1) z;y^t4
^9
page = 1; ljYpMv.>xG
if (page > getPageCount()) aVppOxA
page = getPageCount(); #
cN_ y
int row = (page - 1) * pageSize + 1; _)zmIB(}m
try { ~&DB!6*
rs.absolute(row); 0i5y(m&7
curPage = page; bB:r]*_
s]
} fou_/Nrue
catch (java.sql.SQLException e) { 2&