前言 j Q5 F}
在使用数据库的过程中,不可避免的需要使用到分页的功能,可是JDBC的规范对此却没有很好的解决。对于这个需求很多朋友都有自己的解决方案,比如使用Vector等集合类先保存取出的数据再分页。但这种方法的可用性很差,与JDBC本身的接口完全不同,对不同类型的字段的支持也不好。这里提供了一种与JDBC兼容性非常好的方案。 d Fy$ w=
JDBC和分页 <+oh\y16
Sun的JDBC规范的制定,有时很让人哭笑不得,在JDBC1.0中,对于一个结果集(ResultSet)你甚至只能执行next()操作,而无法让其向后滚动,这就直接导致在只执行一次SQL查询的情况下无法获得结果集的大小。所以,如果你使用的是JDBC1.0的驱动,那么是几乎无法实现分页的。 \9)5b8
好在Sun的JDBC2规范中很好的弥补了这一个不足,增加了结果集的前后滚动操作,虽然仍然不能直接支持分页,但我们已经可以在这个基础上写出自己的可支持分页的ResultSet了。 kGYpJg9=
0Z1ksfLU
ES~b f
u}[ a
和具体数据库相关的实现方法 q!y. cyL
有一些数据库,如Mysql, Oracle等有自己的分页方法,比如Mysql可以使用limit子句,Oracle可以使用ROWNUM来限制结果集的大小和起始位置。这里以Mysql为例,其典型代码如下: mgAjD.
// 计算总的记录条数 yYA*5
7^A
String SQL = SELECT Count(*) AS total + this.QueryPart; V`^*Z}d9
rs = db.executeQuery(SQL); ("2X8(3z
if (rs.next()) M:/NW-:
Total = rs.getInt(1); }=[p>3Dd
// 设置当前页数和总页数 nK1eh@a9Qv
TPages = (int)Math.ceil((double)this.Total/this.MaxLine); .SD-6GVD
CPages = (int)Math.floor((double)Offset/this.MaxLine+1); h0tiWHw
// 根据条件判断,取出所需记录 (p-q>@m
if (Total > 0) { Kjd3!%4mB
SQL = Query + LIMIT + Offset + , + MaxLine; Qr$'Q7
rs = db.executeQuery(SQL); e*7O!Z=O
} .<%tu 0
return rs; >G6kF!V
} IA2VesHb
毫无疑问,这段代码在数据库是Mysql时将会是漂亮的,但是作为一个通用的类(事实上我后面要提供的就是一个通用类库中的一部分),需要适应不同的数据库,而基于这个类(库)的应用,也可能使用不同的数据库,所以,我们将不使用这种方法。 q]?qeF[
1K#>^!?M
^wIB;!W
另一种繁琐的实现方法 TEz;:* ,CG
我看过一些人的做法(事实上包括我在内,一开始也是使用这种方法的),即不使用任何封装,在需要分页的地方,直接操作ResultSet滚到相应的位置,再读取相应数量的记录。其典型代码如下: atTR6%!6
<% L 4j#0I]lq
sqlStmt = sqlCon.createStatement(java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE, =!'9TS
java.sql.ResultSet.CONCUR_READ_ONLY); ~T_|?lU`R
strSQL = select name,age from test; z9aR/:W}
//执行SQL语句并获取结果集 |]?f6^|4
sqlRst = sqlStmt.executeQuery(strSQL); F1#{(uW
//获取记录总数 T+Z[&|
sqlRst.last(); J4T"O<i$58
intRowCount = sqlRst.getRow(); >3!~U.AA'x
//记算总页数 cRLw)"|
intPageCount = (intRowCount+intPageSize-1) / intPageSize; ,HZ%q]*:~
//调整待显示的页码 |?T=4~b
if(intPage>intPageCount) intPage = intPageCount; g0B] ;Y>(
%> @+b$43^
<table border=1 cellspacing=0 cellpadding=0> N%dY.Fk
<tr> q/EX`%U
<th>姓名</th> *9\j1Nd
<th>年龄</th> ?b]zsku8
</tr> xMjhC;i{
<% <_YdN)x
if(intPageCount>0){ u7< +)6-
//将记录指针定位到待显示页的第一条记录上 D$}hoM1
sqlRst.absolute((intPage-1) * intPageSize + 1); gi!_Nz
//显示数据 m_)-
i = 0; wN[lC|1c
while(i<intPageSize && !sqlRst.isAfterLast()){ #X-C~*|>j
%> dn
6]qW5
<tr> 7{m>W!
<td><%=sqlRst.getString(1)%></td> 3``JrkPI
<td><%=sqlRst.getString(2)%></td> 5#.m'a)
</tr> Jt8;ddz
<% KuJ)alD;1
sqlRst.next(); }4C_r'd6
i++; rCPIz<
} %'KRbY
} +#Pb@^6"m
%> ##jJaSxG
</table> k{ qxsNM
很显然,这种方法没有考虑到代码重用的问题,不仅代码数量巨大,而且在代码需要修改的情况下,将会无所适从。 ,Cr%2Wg-
&>jz[3
>Sc yc-n
使用Vector进行分页 0AO^d[v
还见过另一些实现分页的类,是先将所有记录都select出来,然后将ResultSet中的数据都get出来,存入Vector等集合类中,再根据所需分页的大小,页数,定位到相应的位置,读取数据。或者先使用前面提到的两种分页方法,取得所需的页面之后,再存入Vector中。 /8l-@P.o
扔开代码的效率不说,单是从程序结构和使用的方便性上讲,就是很糟糕的。比如,这种做法支持的字段类型有限,int, double, String类型还比较好处理,如果碰到Blob, Text等类型,实现起来就很麻烦了。这是一种更不可取的方案。 AG,><UP
F$t]JM
k4q":}M
一个新的Pageable接口及其实现 @[r[l#4yUi
很显然,看过上面三种实现方法后,我们对新的分页机制有了一个目标,即:不与具体数据库相关;尽可能做到代码重用;尽可能与原JDBC接口的使用方法保持一致;尽可能高的效率。 \!^=~` X-
首先,我们需要提供一个与java.sql.ResultSet向下兼容的接口,把它命名为Pageable,接口定义如下: apL$`{>US
public interface Pageable extends java.sql.ResultSet{ *u+DAg'&
/**返回总页数 |Hf|N$
*/ lh;fqn`
int getPageCount(); K#OL/2^
5
/**返回当前页的记录条数 FyEKqYl
*/ 1/-3m Po
int getPageRowsCount(); %0Ur3
/**返回分页大小 &~_F2]oM
*/ -}6ew@GE
int getPageSize(); UT3Fi@
/**转到指定页 8eB,$;i
*/ kkl'D!z2g
void gotoPage(int page) ; JBpV'_"]
/**设置分页大小 $mJv\;t
*/ .z#eYn%d
void setPageSize(int pageSize); };'@'
/**返回总记录行数 B:"D)/\
*/ 7NvKpinQ
int getRowsCount(); gv67+Mf
/** `3\aX|4@
* 转到当前页的第一条记录 2K:A4)jZ
* @exception java.sql.SQLException 异常说明。 AS;Sz/YP
*/ yY#h1
void pageFirst() throws java.sql.SQLException; EXSJ@k6=8s
/** }c8nn
* 转到当前页的最后一条记录 :?xH)J,imk
* @exception java.sql.SQLException 异常说明。 /h53;$zK
*/ "l&SRX?g
void pageLast() throws java.sql.SQLException; `rn/H;r!Z
/**返回当前页号 T~3{$
*/ zmhc\M?z
int getCurPage(); &{j!!LL
} ?M:>2wl
这是一个对java.sql.ResultSet进行了扩展的接口,主要是增加了对分页的支持,如设置分页大小,跳转到某一页,返回总页数等等。 eA&