首页 > 开发 > JSP > 正文

目前最好的JSP分页技术(考虑到数据库移植,并修正过效率)

2020-02-05 13:37:04
字体:
来源:转载
供稿:网友

  前言

  在使用数据库的过程中,不可避免的需要使用到分页的功能,可是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,这个分页的实现是否对你的编程有帮助呢?仔细看看,其实真正自己写的代码并不多的,大部分都只是简单的转发操作。一个合适的模式应用可以帮你很大忙。

国内最大的酷站演示中心!
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表