前言
在使用数据库的过程中,不可避免的需要使用到分页的功能,可是jdbc的规范对此却没有很好的解决。对于这个需求很多朋友都有自己的解决方案,比如使用vector等集合类先保存取出的数据再分页。但这种方法的可用性很差,与jdbc本身的接口完全不同,对不同类型的字段的支持也不好。这里提供了一种与jdbc兼容性非常好的方案。
jdbc和分页
sun的jdbc规范的制定,有时很让人哭笑不得,在jdbc1.0中,对于一个结果集(resultset)你甚至只能执行next()操作,而无法让其向后滚动,这就直接导致在只执行一次sql查询的情况下无法获得结果集的大小。所以,如果你使用的是jdbc1.0的驱动,那么是几乎无法实现分页的。
好在sun的jdbc2规范中很好的弥补了这一个不足,增加了结果集的前后滚动操作,虽然仍然不能直接支持分页,但我们已经可以在这个基础上写出自己的可支持分页的resultset了。
和具体数据库相关的实现方法
有一些数据库,如mysql, oracle等有自己的分页方法,比如mysql可以使用limit子句,oracle可以使用rownum来限制结果集的大小和起始位置。这里以mysql为例,其典型代码如下:
// 计算总的记录条数
string sql = "select count(*) as total " + this.querypart;
rs = db.executequery(sql);
if (rs.next())
total = rs.getint(1);
// 设置当前页数和总页数
tpages = (int)math.ceil((double)this.total/this.maxline);
cpages = (int)math.floor((double)offset/this.maxline+1);
// 根据条件判断,取出所需记录
if (total > 0) {
sql = query + " limit " + offset + " , " + maxline;
rs = db.executequery(sql);
}
return rs;
}
毫无疑问,这段代码在数据库是mysql时将会是漂亮的,但是作为一个通用的类(事实上我后面要提供的就是一个通用类库中的一部分),需要适应不同的数据库,而基于这个类(库)的应用,也可能使用不同的数据库,所以,我们将不使用这种方法。
另一种繁琐的实现方法
我看过一些人的做法(事实上包括我在内,一开始也是使用这种方法的),即不使用任何封装,在需要分页的地方,直接操作resultset滚到相应的位置,再读取相应数量的记录。其典型代码如下:
<%
sqlstmt = sqlcon.createstatement(java.sql.resultset.type_scroll_insensitive,
java.sql.resultset.concur_read_only);
strsql = "select name,age from test";
//执行sql语句并获取结果集
sqlrst = sqlstmt.executequery(strsql);
//获取记录总数
sqlrst.last();
introwcount = sqlrst.getrow();
//记算总页数
intpagecount = (introwcount+intpagesize-1) / intpagesize;
//调整待显示的页码
if(intpage>intpagecount) intpage = intpagecount;
%>
<table border="1" cellspacing="0" cellpadding="0">
<tr>
<th>姓名</th>
<th>年龄</th>
</tr>
<%
if(intpagecount>0){
//将记录指针定位到待显示页的第一条记录上
sqlrst.absolute((intpage-1) * intpagesize + 1);
//显示数据
i = 0;
while(i<intpagesize && !sqlrst.isafterlast()){
%>
<tr>
<td><%=sqlrst.getstring(1)%></td>
<td><%=sqlrst.getstring(2)%></td>
</tr>
<%
sqlrst.next();
i++;
}
}
%>
</table>
很显然,这种方法没有考虑到代码重用的问题,不仅代码数量巨大,而且在代码需要修改的情况下,将会无所适从。
使用vector进行分页
还见过另一些实现分页的类,是先将所有记录都select出来,然后将resultset中的数据都get出来,存入vector等集合类中,再根据所需分页的大小,页数,定位到相应的位置,读取数据。或者先使用前面提到的两种分页方法,取得所需的页面之后,再存入vector中。
扔开代码的效率不说,单是从程序结构和使用的方便性上讲,就是很糟糕的。比如,这种做法支持的字段类型有限,int, double, string类型还比较好处理,如果碰到blob, text等类型,实现起来就很麻烦了。这是一种更不可取的方案。
一个新的pageable接口及其实现
很显然,看过上面三种实现方法后,我们对新的分页机制有了一个目标,即:不与具体数据库相关;尽可能做到代码重用;尽可能与原jdbc接口的使用方法保持一致;尽可能高的效率。
首先,我们需要提供一个与java.sql.resultset向下兼容的接口,把它命名为pageable,接口定义如下:
public interface pageable extends java.sql.resultset{
/**返回总页数
*/
int getpagecount();
/**返回当前页的记录条数
*/
int getpagerowscount();
/**返回分页大小
*/
int getpagesize();
/**转到指定页
*/
void gotopage(int page) ;
/**设置分页大小
*/
void setpagesize(int pagesize);
/**返回总记录行数
*/
int getrowscount();
/**
* 转到当前页的第一条记录
* @exception java.sql.sqlexception 异常说明。
*/
void pagefirst() throws java.sql.sqlexception;
/**
* 转到当前页的最后一条记录
* @exception java.sql.sqlexception 异常说明。
*/
void pagelast() throws java.sql.sqlexception;
/**返回当前页号
*/
int getcurpage();
}
这是一个对java.sql.resultset进行了扩展的接口,主要是增加了对分页的支持,如设置分页大小,跳转到某一页,返回总页数等等。
接着,我们需要实现这个接口,由于这个接口继承自resultset,并且它的大部分功能也都和resultset原有功能相同,所以这里使用了一个简单的decorator模式。
pageableresultset2的类声明和成员声明如下:
public class pageableresultset2 implements pageable {
protected java.sql.resultset rs=null;
protected int rowscount;
protected int pagesize;
protected int curpage;
protected string command = "";
}
可以看到,在pageableresultset2中,包含了一个resultset的实例(这个实例只是实现了resultset接口,事实上它是由各个数据库厂商分别实现的),并且把所有由resultset继承来的方法都直接转发给该实例来处理。
pageableresultset2中继承自resultset的主要方法:
//……
public boolean next() throws sqlexception {
return rs.next();
}
//……
public string getstring(string columnname) throws sqlexception {
try {
return rs.getstring(columnname);
}
catch (sqlexception e) {//这里是为了增加一些出错信息的内容便于调试
throw new sqlexception (e.tostring()+" columnname="
+columnname+" sql="+this.getcommand());
}
}
//……
只有在pageable接口中新增的方法才需要自己的写方法处理。
/**方法注释可参考pageable.java
*/
public int getcurpage() {
return curpage;
}
public int getpagecount() {
if(rowscount==0) return 0;
if(pagesize==0) return 1;
//calculate pagecount
double tmpd=(double)rowscount/pagesize;
int tmpi=(int)tmpd;
if(tmpd>tmpi) tmpi++;
return tmpi;
}
public int getpagerowscount() {
if(pagesize==0) return rowscount;
if(getrowscount()==0) return 0;
if(curpage!=getpagecount()) return pagesize;
return rowscount-(getpagecount()-1)*pagesize;
}
public int getpagesize() {
return pagesize;
}
public int getrowscount() {
return rowscount;
}
public void gotopage(int page) {
if (rs == null)
return;
if (page < 1)
page = 1;
if (page > getpagecount())
page = getpagecount();
int row = (page - 1) * pagesize + 1;
try {
rs.absolute(row);
curpage = page;
}
catch (java.sql.sqlexception e) {
}
}
public void pagefirst() throws java.sql.sqlexception {
int row=(curpage-1)*pagesize+1;
rs.absolute(row);
}
public void pagelast() throws java.sql.sqlexception {
int row=(curpage-1)*pagesize+getpagerowscount();
rs.absolute(row);
}
public void setpagesize(int pagesize) {
if(pagesize>=0){
this.pagesize=pagesize;
curpage=1;
}
}
//pageableresultset2的构造方法:
public pageableresultset2(java.sql.resultset rs) throws java.sql.sqlexception {
if(rs==null) throw new sqlexception("given resultset is null","user");
rs.last();
rowscount=rs.getrow();
rs.beforefirst();
this.rs=rs;
}
/*如果要提高效率,可以利用select count(*) 语句取得所有记录数,注释掉构造函数的rs.last();rowscount=rs.getrow();rs.beforefirst();三句。在调用构造函数后调用此方法获得所有的记录,参数是select count(*)后的结果集
*/
public void setrowscount(java.sql.resultset rs)throws java.sql.sqlexception {
if(rs==null) throw new sqlexception("given resultset is null","user");
rowcount=rs.getint(1);
}
这里只是简单的取得一个总记录数,并将记录游标移回初始位置(before first),同时将参数中的resultset赋给成员变量。
这里只是简单的取得一个总记录数,并将记录游标移回初始位置(before first),同时将参数中的resultset赋给成员变量。
pageable的使用方法
因为pageable接口继承自resultset,所以在使用方法上与resultset一致,尤其是在不需要分页功能的时候,可以直接当成resultset使用。而在需要分页时,只需要简单的setpagesize, gotopage,即可。
preparedstatement pstmt=null;
pageable rs=null;
……//构造sql,并准备一个pstmt.
rs=new pageableresultset2(pstmt.executequery());//构造一个pageable
rs.setpagesize(20);//每页20个记录
rs.gotopage(2);//跳转到第2页
for(int i=0; i<rs.getpagerowscount(); i++){//循环处理
int id=rs.getint(“id”);
……//继续处理
rs.next();
}
总结
一个好的基础类应该是便于使用,并且具备足够的可移植性,同时要保证其功能的完善。在上面的实现中,我们从java.sql.resultset接口继承出pageable,并实现了它。这就保证了在使用中与jdbc原有操作的一致性,同时对原有功能没有缩减。
同时它也是易于使用的,因为封装了一切必要的操作,所以在你的代码中唯一显得"难看"和"不舒服"的地方就是需要自己去构造一个pageableresultset2。不过只要你愿意,这也是可以解决的。
当然它也有具有充分的可移植性,当你将数据库由oracle变为mysql或者sqlserver的时候,你仍然可以使用这些分页的代码。它在使用中(或者说在移植的过程中)唯一的限制就是你必须要使用一个支持jdbc2的驱动(现在明白为什么我把类命名为pageableresultset2了吧。:p),不过,好在jdbc2已经成为标准了,绝大多数的数据库(如oracle, mysql, sqlserver)都有自己的或者第三方提供的jdbc2的驱动。
ok,这个分页的实现是否对你的编程有帮助呢?仔细看看,其实真正自己写的代码并不多的,大部分都只是简单的转发操作。一个合适的模式应用可以帮你很大忙。
国内最大的酷站演示中心!新闻热点
疑难解答