前言 OndhLLz
在使用数据库的过程中,不可避免的需要使用到分页的功能,可是JDBC的规范对此却没有很好的解决。对于这个需求很多朋友都有自己的解决方案,比如使用Vector等集合类先保存取出的数据再分页。但这种方法的可用性很差,与JDBC本身的接口完全不同,对不同类型的字段的支持也不好。这里提供了一种与JDBC兼容性非常好的方案。 mqfO4"lt
JDBC和分页 c~<1':
Sun的JDBC规范的制定,有时很让人哭笑不得,在JDBC1.0中,对于一个结果集(ResultSet)你甚至只能执行next()操作,而无法让其向后滚动,这就直接导致在只执行一次SQL查询的情况下无法获得结果集的大小。所以,如果你使用的是JDBC1.0的驱动,那么是几乎无法实现分页的。 (x;g/!:
好在Sun的JDBC2规范中很好的弥补了这一个不足,增加了结果集的前后滚动操作,虽然仍然不能直接支持分页,但我们已经可以在这个基础上写出自己的可支持分页的ResultSet了。 hIJ)MZU|
~^)^q8
`A/j1UWJ
wzjU,Mwe
和具体数据库相关的实现方法 w>xV
有一些数据库,如Mysql, Oracle等有自己的分页方法,比如Mysql可以使用limit子句,Oracle可以使用ROWNUM来限制结果集的大小和起始位置。这里以Mysql为例,其典型代码如下: ]+DI.%
// 计算总的记录条数 .w6eJ4]
String SQL = SELECT Count(*) AS total + this.QueryPart; O)R(==P26P
rs = db.executeQuery(SQL); uqyB5V0gh
if (rs.next()) "k$JP
Total = rs.getInt(1); d h^^G^
// 设置当前页数和总页数 iO1nwl !#
TPages = (int)Math.ceil((double)this.Total/this.MaxLine); aH_6s4+:
CPages = (int)Math.floor((double)Offset/this.MaxLine+1); hbOnlj4
// 根据条件判断,取出所需记录 +~sd"v6
if (Total > 0) { I-NN29Sk
SQL = Query + LIMIT + Offset + , + MaxLine; _ia! mT<
rs = db.executeQuery(SQL); E{Pgf8
} !.5),2
return rs; !SHj$Jwa'
} 7@%'wy&A
毫无疑问,这段代码在数据库是Mysql时将会是漂亮的,但是作为一个通用的类(事实上我后面要提供的就是一个通用类库中的一部分),需要适应不同的数据库,而基于这个类(库)的应用,也可能使用不同的数据库,所以,我们将不使用这种方法。 _L.n,
% 0:p)Z0
7yI@"c#O
另一种繁琐的实现方法 -m 5}#P89
我看过一些人的做法(事实上包括我在内,一开始也是使用这种方法的),即不使用任何封装,在需要分页的地方,直接操作ResultSet滚到相应的位置,再读取相应数量的记录。其典型代码如下: Zs zs1{t
<% (y4#.vZh:
sqlStmt = sqlCon.createStatement(java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE, 2_QN&o ~h
java.sql.ResultSet.CONCUR_READ_ONLY); d6 _C"r
strSQL = select name,age from test; h7_)%U<J2
//执行SQL语句并获取结果集 %1McD{
sqlRst = sqlStmt.executeQuery(strSQL); ts9pM~_~
//获取记录总数 +UWU|:
sqlRst.last(); BRG|Asg(
intRowCount = sqlRst.getRow(); Ek.&Sf$cd'
//记算总页数 B`#h{ )[
intPageCount = (intRowCount+intPageSize-1) / intPageSize; -dTLunv
//调整待显示的页码 ET^ |z
if(intPage>intPageCount) intPage = intPageCount; _q>SE1j+W=
%> mZ0J!QYk
<table border=1 cellspacing=0 cellpadding=0> pF=g||gS
<tr> cm>E[SHr
<th>姓名</th> K=u0nrG*
<th>年龄</th> m)?5}ZwAH
</tr> 1@sM1WMX
<% J_#R 87
if(intPageCount>0){ #$'"cfRxc
//将记录指针定位到待显示页的第一条记录上 j;P+_Hfe/E
sqlRst.absolute((intPage-1) * intPageSize + 1); Xwu.AVsr
//显示数据 NSLVD[yT
i = 0; `m%dX'0E
while(i<intPageSize && !sqlRst.isAfterLast()){ GSVdb/+
%> `QP
~
<tr> {M~lbU
<td><%=sqlRst.getString(1)%></td> V`a+Hi<P\
<td><%=sqlRst.getString(2)%></td> 2C+(":=}
</tr> U_z2J(e~
<% T>]sQPg
sqlRst.next(); t)1phg4H)
i++; JSMPyj
} p_terD:
} dXu {p
%> f5dR 5G
</table> l`n5~Fs
很显然,这种方法没有考虑到代码重用的问题,不仅代码数量巨大,而且在代码需要修改的情况下,将会无所适从。 a,Kky^B
q7]>i!A
R e:T9K'e
使用Vector进行分页 ?KN:r E
还见过另一些实现分页的类,是先将所有记录都select出来,然后将ResultSet中的数据都get出来,存入Vector等集合类中,再根据所需分页的大小,页数,定位到相应的位置,读取数据。或者先使用前面提到的两种分页方法,取得所需的页面之后,再存入Vector中。 0~E 6QhV:
扔开代码的效率不说,单是从程序结构和使用的方便性上讲,就是很糟糕的。比如,这种做法支持的字段类型有限,int, double, String类型还比较好处理,如果碰到Blob, Text等类型,实现起来就很麻烦了。这是一种更不可取的方案。 DR+,Y2!_GT
\%_ZV9cKF
r)l`
一个新的Pageable接口及其实现 :
1)}Epo,
很显然,看过上面三种实现方法后,我们对新的分页机制有了一个目标,即:不与具体数据库相关;尽可能做到代码重用;尽可能与原JDBC接口的使用方法保持一致;尽可能高的效率。 '
lo.h""
首先,我们需要提供一个与java.sql.ResultSet向下兼容的接口,把它命名为Pageable,接口定义如下: wgd<3 X
public interface Pageable extends java.sql.ResultSet{ _3^y|_!
/**返回总页数 I^0t2[M
*/ <DiOWi
int getPageCount(); R ZcH+?7
/**返回当前页的记录条数 bcJ@-i0V
*/ ]
VG?+
int getPageRowsCount(); saK;[&I*
/**返回分页大小 (ppoW
*/ a>Re^GT+z
int getPageSize(); b&t[S[P.V
/**转到指定页 2>y:N.
*/ @5Qoi~o
void gotoPage(int page) ; F,Fo}YQX
/**设置分页大小 fNhT;Bux
*/ I"Q<n[g0'
void setPageSize(int pageSize); 5j[#'3TSU
/**返回总记录行数 wL
eHQ]
*/ !]DuZ=
int getRowsCount(); )bW<8f2
/** X=_Z(;<&
* 转到当前页的第一条记录 kO3`54
* @exception java.sql.SQLException 异常说明。 H@!#;w
*/ D9,!
%7i
void pageFirst() throws java.sql.SQLException; m6so]xr
/** )A83A<~
* 转到当前页的最后一条记录 #MM&BC
* @exception java.sql.SQLException 异常说明。 IRB& j%LA
*/ %-^}45](q
void pageLast() throws java.sql.SQLException; 9/;{>RL=
/**返回当前页号 !k h{9I>M
*/ $N\+,?
int getCurPage(); q+/l"&j.
} BjD&>gO)
这是一个对java.sql.ResultSet进行了扩展的接口,主要是增加了对分页的支持,如设置分页大小,跳转到某一页,返回总页数等等。 EzP#Mnz^
接着,我们需要实现这个接口,由于这个接口继承自ResultSet,并且它的大部分功能也都和ResultSet原有功能相同,所以这里使用了一个简单的Decorator模式。 bXl8v
PageableResultSet2的类声明和成员声明如下: AVpuMNd@
public class PageableResultSet2 implements Pageable { Ow3a0cF[9
protected java.sql.ResultSet rs=null; ,C!n}+27
protected int rowsCount; 3X'WR]
protected int pageSize; eY3=|RR
protected int curPage; |!b9b(_j9
protected String command = ; ?M"HXu
} IQ{?_'
可以看到,在PageableResultSet2中,包含了一个ResultSet的实例(这个实例只是实现了ResultSet接口,事实上它是由各个数据库厂商分别实现的),并且把所有由ResultSet继承来的方法都直接转发给该实例来处理。 9t}xXk
PageableResultSet2中继承自ResultSet的主要方法: 8eww7k^R
//…… G2@KI-
public boolean next() throws SQLException { a/e\vwHLv
return rs.next(); ;eR{tH /4
} qc-C>Ra
//…… |BJqy/
public String getString(String columnName) throws SQLException { z6Z='=pT
try { #<}kISV0
return rs.getString(columnName); QN #)F
} :0dfB&7
catch (SQLException e) {//这里是为了增加一些出错信息的内容便于调试 ;Uk!jQh
throw new SQLException (e.toString()+ columnName= u%aFb*
+columnName+ SQL=+this.getCommand()); M71R -B`-
} .;Z.F7{q
} 5&%fkZ0
//…… ((9YG
只有在Pageable接口中新增的方法才需要自己的写方法处理。 [tN` :}?
/**方法注释可参考Pageable.java Ut;'Gk
*/ z@`@I
public int getCurPage() { pX]21&F
return curPage; 3Q$c'C
} 4
m$sJ
public int getPageCount() { |$Xf;N37t
if(rowsCount==0) return 0; J" wKR y
if(pageSize==0) return 1; {e6KJ@H6
//calculate PageCount %#4 +!
double tmpD=(double)rowsCount/pageSize; =BW9/fG
int tmpI=(int)tmpD; GWh|FEqUbf
if(tmpD>tmpI) tmpI++; 9TW8o}k`
return tmpI; yjv&4pIc1
} $P_x v
public int getPageRowsCount() { ]W|RtdF3.N
if(pageSize==0) return rowsCount; K Dz]wNf
if(getRowsCount()==0) return 0; aZxO/b^j
if(curPage!=getPageCount()) return pageSize; r$?Vx_f`Q
return rowsCount-(getPageCount()-1)*pageSize; i"fCpkAP
} ;r=?BbND?
public int getPageSize() { x!`KhTu`_A
return pageSize; >DS}#'N4l
} w%I8CU_}.
public int getRowsCount() { cS
4T\{B;
return rowsCount; u!u5g.Q
} ,N;v~D$Y
public void gotoPage(int page) { h;}ODK(.
if (rs == null) }(cY|
return; l }+Cdy9>
if (page < 1) 5])8qb/F
page = 1; *sAOpf@M
if (page > getPageCount()) ytob/tc
page = getPageCount(); 'M
lXnHxt
int row = (page - 1) * pageSize + 1; k?n]ZNlT
try { 8iOO1I?+
rs.absolute(row); s%bUgO%&
curPage = page; cyHhy_~R
} M0L-u
catch (java.sql.SQLException e) { 7>KQRLw
} Fi/jR0]e2
} [{/$9k-aF?
public void pageFirst() throws java.sql.SQLException { ef,F[-2^o
int row=(curPage-1)*pageSize+1; Ki63Ox^O
rs.absolute(row); ^K/G 5
} iU,/!IQ
public void pageLast() throws java.sql.SQLException { _4Ii5CNNU
int row=(curPage-1)*pageSize+getPageRowsCount(); 8}9Ob~on
rs.absolute(row); Djyp3uUA/
} e
%&
public void setPageSize(int pageSize) { :=Nb=&lst
if(pageSize>=0){ M(NH9EE
this.pageSize=pageSize; +yiU@K).0
curPage=1; h\2}875
} p^Agh
} -2z,cj&E{
PageableResultSet2的构造方法: "C& J wm?
public PageableResultSet2(java.sql.ResultSet rs) throws java.sql.SQLException { 9G+y.^/6
if(rs==null) throw new SQLException(given ResultSet is NULL,user); !&\meS{
a.1`\$]d
rs.last(); VZIKjrKs
rowsCount=rs.getRow(); uGM>C"
rs.beforeFirst(); ?&XzW+(X
E"ZEo9y@^
this.rs=rs; #[Z<