前言 m\/) m]wR
在使用数据库的过程中,不可避免的需要使用到分页的功能,可是JDBC的规范对此却没有很好的解决。对于这个需求很多朋友都有自己的解决方案,比如使用Vector等集合类先保存取出的数据再分页。但这种方法的可用性很差,与JDBC本身的接口完全不同,对不同类型的字段的支持也不好。这里提供了一种与JDBC兼容性非常好的方案。 YWD gRb
JDBC和分页 j8bA"r1
Sun的JDBC规范的制定,有时很让人哭笑不得,在JDBC1.0中,对于一个结果集(ResultSet)你甚至只能执行next()操作,而无法让其向后滚动,这就直接导致在只执行一次SQL查询的情况下无法获得结果集的大小。所以,如果你使用的是JDBC1.0的驱动,那么是几乎无法实现分页的。 I>vU;xV\m
好在Sun的JDBC2规范中很好的弥补了这一个不足,增加了结果集的前后滚动操作,虽然仍然不能直接支持分页,但我们已经可以在这个基础上写出自己的可支持分页的ResultSet了。 h&v].l
3O1Lv2)_
2EN}"Du]mj
Ui9;rh$1eU
和具体数据库相关的实现方法 br
3-.g
有一些数据库,如Mysql, Oracle等有自己的分页方法,比如Mysql可以使用limit子句,Oracle可以使用ROWNUM来限制结果集的大小和起始位置。这里以Mysql为例,其典型代码如下: ycki0&n3
// 计算总的记录条数 ,`!lZ|
U
String SQL = SELECT Count(*) AS total + this.QueryPart; 02tN=}Cj)
rs = db.executeQuery(SQL); @qjN>PH~
if (rs.next()) bi+g=cS
Total = rs.getInt(1); "rEfhzmyF
// 设置当前页数和总页数 jq8TfJ|
TPages = (int)Math.ceil((double)this.Total/this.MaxLine); ,_w}\'?L
CPages = (int)Math.floor((double)Offset/this.MaxLine+1); *P]]7DR
// 根据条件判断,取出所需记录 .d$Q5Qae
if (Total > 0) { D+! S\~u
SQL = Query + LIMIT + Offset + , + MaxLine; |8[!`T*s
rs = db.executeQuery(SQL); 2J$vX(
} .0gfP4{1{
return rs; *=v%($~PK6
} w^ofH-R/
毫无疑问,这段代码在数据库是Mysql时将会是漂亮的,但是作为一个通用的类(事实上我后面要提供的就是一个通用类库中的一部分),需要适应不同的数据库,而基于这个类(库)的应用,也可能使用不同的数据库,所以,我们将不使用这种方法。 Z)=S>06X Q
ePI N<F;I
ydY 7 :D
另一种繁琐的实现方法 a.JjbFL
我看过一些人的做法(事实上包括我在内,一开始也是使用这种方法的),即不使用任何封装,在需要分页的地方,直接操作ResultSet滚到相应的位置,再读取相应数量的记录。其典型代码如下: |22vNt_
<% `'EG7
sqlStmt = sqlCon.createStatement(java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE, qdKqc,R1{
java.sql.ResultSet.CONCUR_READ_ONLY); ^;( dF<?'r
strSQL = select name,age from test; 4b`Fi@J\
//执行SQL语句并获取结果集 "AKr;|m
sqlRst = sqlStmt.executeQuery(strSQL); \v<S:cTf
//获取记录总数 3PL0bejaT7
sqlRst.last(); }lhk;#r
intRowCount = sqlRst.getRow(); >=:mtcph
//记算总页数 M6qNh`+HO
intPageCount = (intRowCount+intPageSize-1) / intPageSize; G,^ ?qbHg
//调整待显示的页码 m^m=/'<+
if(intPage>intPageCount) intPage = intPageCount; *icaKy3
%> n+Conp/
<table border=1 cellspacing=0 cellpadding=0> 9mv0} I
<tr> %{cVG-<_iz
<th>姓名</th> :V#xrH8R
<th>年龄</th> omy3<6
</tr> iyr8*L\
<% 99By.+~pX
if(intPageCount>0){ O0`ofFN
//将记录指针定位到待显示页的第一条记录上 AFvv+
ss
sqlRst.absolute((intPage-1) * intPageSize + 1); 5rCJIl.
//显示数据 f?GoBh<
i = 0; $v e$Sq
while(i<intPageSize && !sqlRst.isAfterLast()){ i[FYR;C
%> tSoF!@6
<tr> y:$qX*+9e
<td><%=sqlRst.getString(1)%></td> 9,\AAISi
<td><%=sqlRst.getString(2)%></td> q+<,FdG
</tr>
$?gKIv>g
<% r2i]9>w
sqlRst.next(); /YJBRU2
i++; J&JZYuuf
} @W
@,8e]c
} zw$\d1-+h
%> mJ5%+.V
</table> Iw(
wT_
很显然,这种方法没有考虑到代码重用的问题,不仅代码数量巨大,而且在代码需要修改的情况下,将会无所适从。 Knb(MI6
b2[U3)|oO
1{d;Ngx
使用Vector进行分页 yI07E "9
还见过另一些实现分页的类,是先将所有记录都select出来,然后将ResultSet中的数据都get出来,存入Vector等集合类中,再根据所需分页的大小,页数,定位到相应的位置,读取数据。或者先使用前面提到的两种分页方法,取得所需的页面之后,再存入Vector中。 Fn4yx~0
扔开代码的效率不说,单是从程序结构和使用的方便性上讲,就是很糟糕的。比如,这种做法支持的字段类型有限,int, double, String类型还比较好处理,如果碰到Blob, Text等类型,实现起来就很麻烦了。这是一种更不可取的方案。 O:T
49:R}r
|*h{GX.(
|]?W`KN0
一个新的Pageable接口及其实现 8f)pf$v`
很显然,看过上面三种实现方法后,我们对新的分页机制有了一个目标,即:不与具体数据库相关;尽可能做到代码重用;尽可能与原JDBC接口的使用方法保持一致;尽可能高的效率。 fi ~@J`
首先,我们需要提供一个与java.sql.ResultSet向下兼容的接口,把它命名为Pageable,接口定义如下: )t7MD(
public interface Pageable extends java.sql.ResultSet{ GVn'p
Wg
/**返回总页数 7
<]YK`a2d
*/ Z(R0IW
int getPageCount(); [P ;fv
/**返回当前页的记录条数 BzWkZAX
*/ QkHG`yW
int getPageRowsCount(); QLHEzEvf{/
/**返回分页大小 gae=+@z
*/ 5T( cy
int getPageSize(); 7,Z<PE
/**转到指定页 ZHeq)5C ;f
*/ ;/?w-)n?
void gotoPage(int page) ; F|.tn`j]U
/**设置分页大小 y x#ub-A8
*/ _zzNF93Bn
void setPageSize(int pageSize); !?+0O]`}
/**返回总记录行数 Xc"
%-
*/ 8No'8(dPX
int getRowsCount(); `Eu,SvkF w
/** h>cjRH?e
* 转到当前页的第一条记录 cT/mi":8{
* @exception java.sql.SQLException 异常说明。 %0}}Qt
*/ 3$5E1*ed
void pageFirst() throws java.sql.SQLException; /Lm~GmPt
/** u#^l9/tl
* 转到当前页的最后一条记录 iPWr-
* @exception java.sql.SQLException 异常说明。 w{*V8S3h9
*/ @o'L! 5Y
void pageLast() throws java.sql.SQLException; 9h)8Mq+M
/**返回当前页号 :~srl)|)
*/ *HGhm04F{
int getCurPage(); v+79#qWK|n
} c9CFGo?)N
这是一个对java.sql.ResultSet进行了扩展的接口,主要是增加了对分页的支持,如设置分页大小,跳转到某一页,返回总页数等等。 '
;nG4+K
接着,我们需要实现这个接口,由于这个接口继承自ResultSet,并且它的大部分功能也都和ResultSet原有功能相同,所以这里使用了一个简单的Decorator模式。 o.Y6(o
PageableResultSet2的类声明和成员声明如下: s] ;P<
public class PageableResultSet2 implements Pageable { I} +up,B]o
protected java.sql.ResultSet rs=null; um_J%v6ER
protected int rowsCount; y3QS!3I
protected int pageSize; !io1~GpKS
protected int curPage; Jq? zr]"A
protected String command = ; >g~IP>
} ^P]5@d v
可以看到,在PageableResultSet2中,包含了一个ResultSet的实例(这个实例只是实现了ResultSet接口,事实上它是由各个数据库厂商分别实现的),并且把所有由ResultSet继承来的方法都直接转发给该实例来处理。 pBv,,d`
PageableResultSet2中继承自ResultSet的主要方法: }oSgx
//…… N$C+le
public boolean next() throws SQLException { Eaxsg
return rs.next(); }m5()@Q}a
} Q{'4,J-w
//…… M3F1O6=4j
public String getString(String columnName) throws SQLException { K[/L!.Ag
try { S-~)|7d.
return rs.getString(columnName); Dr=$ }Y
} >BK/HuS
catch (SQLException e) {//这里是为了增加一些出错信息的内容便于调试 kw gLK@@%1
throw new SQLException (e.toString()+ columnName= BYhiP/^
+columnName+ SQL=+this.getCommand()); x^pt^KR;
} #G`K<%{?f
} 5VQ-D`kE+
//…… B>=D$*_
只有在Pageable接口中新增的方法才需要自己的写方法处理。 =2NrmwWZs
/**方法注释可参考Pageable.java W+U0Y,N6
*/ pYr+n9)^
public int getCurPage() { cjO%X
return curPage; LYd:S
} oqhJ2
public int getPageCount() { J=: \b
if(rowsCount==0) return 0; Q^3{L\6_
if(pageSize==0) return 1; y0&vsoT
//calculate PageCount -vY5h%7kf
double tmpD=(double)rowsCount/pageSize; t?PqfVSq
int tmpI=(int)tmpD; |mbD q\U
if(tmpD>tmpI) tmpI++; &.s.g\
return tmpI; 3T,[
} XK@&$~iA3
public int getPageRowsCount() { )S`[ gK
if(pageSize==0) return rowsCount; f>4|>kS
if(getRowsCount()==0) return 0; Kn= EDtg
if(curPage!=getPageCount()) return pageSize; }' Y)"8AIA
return rowsCount-(getPageCount()-1)*pageSize; v'Ehr**]+
} 6~2upy~e
public int getPageSize() { r%iFsV_
return pageSize; w4}Q6_0v
} lA1
public int getRowsCount() { +Ss3Ph
return rowsCount; rWDD$4y
} #>,E"-]f
public void gotoPage(int page) { ePJ_O~c
if (rs == null) \)o.Y
zAo@
return; 5p:BHw;%;
if (page < 1) -@`Ah|m@}
page = 1; ~I;x_0iY4
if (page > getPageCount()) (jj`}Qe3U
page = getPageCount(); b6Z3(!]
]
int row = (page - 1) * pageSize + 1; .|hsn6i/-
try { WG\
_eRj
rs.absolute(row); X;UEq]kcmn
curPage = page; YaC[S^p
} iDl#foXa`
catch (java.sql.SQLException e) { Cojs;`3iF:
} }+pwSjsno
} tvFe_*Ck
public void pageFirst() throws java.sql.SQLException { ~TS!5Wiv
int row=(curPage-1)*pageSize+1; ITqAy1m@C
rs.absolute(row); Sa[lYMuB
} 8IxIW0
public void pageLast() throws java.sql.SQLException { 6B7*|R>
int row=(curPage-1)*pageSize+getPageRowsCount(); <Lxp t
rs.absolute(row); !uIY ,
} 4uMMf
public void setPageSize(int pageSize) { 'Q :%s
if(pageSize>=0){ +75"Q:I
this.pageSize=pageSize; %cUC~, g_(
curPage=1; EsX(<bx
} ,tg]Gt
} ^gp]tAf
PageableResultSet2的构造方法: FGyrDRDwC
public PageableResultSet2(java.sql.ResultSet rs) throws java.sql.SQLException { _a& Z$2O
if(rs==null) throw new SQLException(given ResultSet is NULL,user); \z FCph4
]u$tKC
rs.last(); V59!}kel1%
rowsCount=rs.getRow(); 3%GsTq2o
rs.beforeFirst(); A- Abj'
6Y,&