至于缓存结果集ResultSet的方法则完全是一种错误的做法。因为ResultSet在Statement或Connection关闭时也会被关闭,如果要使ResultSet有效势必长时间占用数据库连接。
因此比较好的分页做法应该是每次翻页的时候只从数据库里检索页面大小的块区的数据。这样虽然每次翻页都需要查询数据库,但查询出的记录数很少,网络传输数据量不大,如果使用连接池更可以略过最耗时的建立数据库连接过程。而在数据库端有各种成熟的优化技术用于提高查询速度,比在应用服务器层做缓存有效多了。
所以我用到了从数据库中查询当前页面记录的办法。由于设计到公司核心代码的缘故,我就简单的用一个图书的例子介绍一下是我的思路:
一开发思路:
我是用Struts的,当然就要使用到MVC这个模式,分页的时候也是这样的。
首先要有一个和数据库链接的bean,我们暂时叫DBUtil吧,这里面封装了很多和数据库有关的东西,比如有查询,修改,插入等方法,这是一个基本的类,这里我们用到的是查询,这个方法返回的数据类型是Object[][]。这里大家要注意一下,你也可以返回别的类型,但是一定要注意把后面对应的程序也修改一下。一下是这个类的部分代码:
packagecom.model;
importcom.attribute.Constants;
importjava.sql.*;
importjava.util.*;
/**
*CreatedbyIntelliJIDEA.
*User:7612CE
*Date:2005-6-2
*Time:21:41:38
*TochangethistemplateuseOptions|FileTemplates.
*/
publicclassDBUtil{
StringsDBDriver=Constants.DBDriver;
Stringurl=Constants.DBUrl;
StringdbUser=Constants.DBUser;
StringdbPassword=Constants.DBPassword;
Connectionconn=null;
PreparedStatementstmt=null;
ResultSetrs=null;
publicDBUtil()throwsClassNotFoundException{
try{
Class.forName(sDBDriver);
conn=DriverManager.getConnection(url,dbUser,dbPassword);
}catch(Exceptione){
System.out.println("DBUtil():"+e.getMessage());
}
}
/**
*Searchsomerecordinobjecttable
*@paramsqlsqlsegment
*@parammapvaluesformatch
*@returnCollection
*/
publicObject[][]doSearch(Stringsql,Object[]data)throwsSQLException{
PreparedStatementstmt=conn.prepareStatement(sql);
for(inti=0;data!=null&&i
System.out.print("theaqlis="+sql);
System.out.println("datais"+data[i]);
stmt.setObject(i+1,data[i]);
}
ResultSetrset=stmt.executeQuery();
ResultSetMetaDatarsm=rset.getMetaData();
intcol=rsm.getColumnCount();
ArrayListlist=newArrayList();
//Eachelementoflistcontainsarecordofresultset
while(rset.next()){
list.add(getLine(rset,col));
}
if(list.size()==0||col==0){
closePrepStmt();
returnnull;
}
closePrepStmt();
//Constructboxasadatamatrix
Object[][]box=newObject[list.size()][col];
for(inti=0;i
for(intj=0;j
{
box[i][j]=((Object[])list.get(i))[j];
}
returnbox;
}
由于是写分页的,当然也是要有一个page的类,具体代码如下,由于有很多注释,不用一一介绍了:
packagecom.util;
/**
*Title:分页对象
*Description:用于包含数据及分页信息的对象
*Page类实现了用于显示分页信息的基本方法,但未指定所含数据的类型,
*可根据需要实现以特定方式组织数据的子类,
*如RowSetPage以RowSet封装数据,ListPage以List封装数据
*Copyright:Copyright(c)2002
*@authorevan_zhao@hotmail.com
*@version1.0
*/
publicclassPageimplementsjava.io.Serializable{
publicstaticfinalPageEMPTY_PAGE=newPage();
publicstaticfinalintDEFAULT_PAGE_SIZE=2;
publicstaticfinalintMAX_PAGE_SIZE=2;
privateintmyPageSize=DEFAULT_PAGE_SIZE;
privateintstart;
privateintavaCount,totalSize;
privateObject[][]data=null;
privateintcurrentPageno;
privateinttotalPageCount;
/**
*默认构造方法,只构造空页
*/
publicPage(){
this.init(0,0,0,DEFAULT_PAGE_SIZE,null);
}
/**
*构造分页对象
*@paramcrs包含一页数据的OracleCachedRowSet
*@paramstart该页数据在数据库中的起始位置
*@paramtotalSize数据库中包含的记录总数
*@parampageSize本页能容纳的记录数
*/
publicPage(Object[][]crs,intstart,inttotalSize,intpageSize){
try{
intavaCount=0;
if(crs!=null){
avaCount=crs.length;
}
data=crs;
this.init(start,avaCount,totalSize,pageSize,data);
}catch(Exceptionex){
thrownewRuntimeException(ex.toString());
}
}
/**
*分页数据初始方法
*@paramstart本页数据在数据库中的起始位置
*@paramavaCount本页包含的数据条数
*@paramtotalSize数据库中总记录条数
*@parampageSize本页容量
*@paramdata本页包含的数据
*/
protectedvoidinit(intstart,intavaCount,inttotalSize,intpageSize,Object[][]data){
this.avaCount=avaCount;
this.myPageSize=pageSize;
this.start=start;
this.totalSize=totalSize;
this.data=data;
//System.out.println("avaCount:"+avaCount);
//System.out.println("totalSize:"+totalSize);
if(avaCount>totalSize){
//thrownewRuntimeException("记录条数大于总条数?!");
}
this.currentPageno=(start-1)/pageSize+1;
this.totalPageCount=(totalSize+pageSize-1)/pageSize;
if(totalSize==0&&avaCount==0){
this.currentPageno=1;
this.totalPageCount=1;
}
//System.out.println("StartIndextoPageNo:"+start+"-"+currentPageno);
}
publicObject[][]getData(){
returnthis.data;
}
/**
*取本页数据容量(本页能包含的记录数)
*@return本页能包含的记录数
*/
publicintgetPageSize(){
returnthis.myPageSize;
}
/**
*是否有下一页
*@return是否有下一页
*/
publicbooleanhasNextPage(){
/*
if(avaCount==0&&totalSize==0){
returnfalse;
}
return(start+avaCount-1)
*/
return(this.getCurrentPageNo()
}
/**
*是否有上一页
*@return是否有上一页
*/
publicbooleanhasPreviousPage(){
/*
returnstart>1;
*/
return(this.getCurrentPageNo()>1);
}
/**
*获取当前页第一条数据在数据库中的位置
*@return
*/
publicintgetStart(){
returnstart;
}
/**
*获取当前页最后一条数据在数据库中的位置
*@return
*/
publicintgetEnd(){
intend=this.getStart()+this.getSize()-1;
if(end<0){
end=0;
}
returnend;
}
/**
*获取上一页第一条数据在数据库中的位置
*@return记录对应的rownum
*/
publicintgetStartOfPreviousPage(){
returnMath.max(start-myPageSize,1);
}
/**
*获取下一页第一条数据在数据库中的位置
*@return记录对应的rownum
*/
publicintgetStartOfNextPage(){
returnstart+avaCount;
}
/**
*获取任一页第一条数据在数据库中的位置,每页条数使用默认值
*@parampageNo页号
*@return记录对应的rownum
*/
publicstaticintgetStartOfAnyPage(intpageNo){
returngetStartOfAnyPage(pageNo,DEFAULT_PAGE_SIZE);
}
/**
*获取任一页第一条数据在数据库中的位置
*@parampageNo页号
*@parampageSize每页包含的记录数
*@return记录对应的rownum
*/
publicstaticintgetStartOfAnyPage(intpageNo,intpageSize){
intstartIndex=(pageNo-1)*pageSize+1;
if(startIndex<1)startIndex=1;
//System.out.println("PageNotoStartIndex:"+pageNo+"-"+startIndex);
returnstartIndex;
}
/**
*取本页包含的记录数
*@return本页包含的记录数
*/
publicintgetSize(){
returnavaCount;
}
/**
*取数据库中包含的总记录数
*@return数据库中包含的总记录数
*/
publicintgetTotalSize(){
returnthis.totalSize;
}
/**
*取当前页码
*@return当前页码
*/
publicintgetCurrentPageNo(){
returnthis.currentPageno;
}
/**
*取总页码
*@return总页码
*/
publicintgetTotalPageCount(){
returnthis.totalPageCount;
}
}
由于是用mvc这样的框架,所以在c这一层仅仅起到一个转向和收集页面信息的作用,所以这里我有写了一个分页查询的bean,暂时命名是PageCtBean,代码如下:
packagecom.util;
importcom.model.DBUtil;
importjava.sql.*;
/**
*CreatedbyIntelliJIDEA.
*User:7612ce
*Date:2005-6-23
*Time:10:36:57
*TochangethistemplateuseOptions|FileTemplates.
*/
publicclassPageCtBean{
publicfinalstaticintMAX_PAGE_SIZE=Page.MAX_PAGE_SIZE;
protectedStringcountSQL,querySQL;
protectedintpageNo,pageSize,startIndex,totalCount;
protectedjavax.sql.RowSetrowSet;
protectedPagesetPage;
protectedObject[][]objTables=null;
protectedObject[][]objCount=null;
protectedObject[]obj=null;
publicPageCtBean(){
}
/**
*构造一查询出所有数据的PageStatement
.*@paramsqlquerysql
*/
publicPageCtBean(Stringsql){
this(sql,1,MAX_PAGE_SIZE);
}
/**
*构造一查询出当页数据的PageStatement
*@paramsqlquerysql
*@parampageNo页码
*/
publicPageCtBean(Stringsql,intpageNo){
this(sql,pageNo,Page.DEFAULT_PAGE_SIZE);
}
/**
*构造一查询出当页数据的PageStatement,并指定每页显示记录条数
*@paramsqlquerysql
*@parampageNo页码
*@parampageSize每页容量
*/
publicPageCtBean(Stringsql,intpageNo,intpageSize){
this.pageNo=pageNo;
this.pageSize=pageSize;
this.startIndex=Page.getStartOfAnyPage(pageNo,pageSize);
this.querySQL=intiQuerySQL(sql,this.startIndex,pageSize);
}
/**
*生成查询一页数据的sql语句
*@paramsql原查询语句
*@startIndex开始记录位置
*@size需要获取的记录数
*/
protectedStringintiQuerySQL(Stringsql,intstartIndex,intsize){
StringBufferquerySQL=newStringBuffer();
querySQL.append(sql)
.append("limit")
.append(startIndex-1)
.append(",").append(size);
returnquerySQL.toString();
}
/**
*使用给出的对象设置指定参数的值
*@paramobj包含参数值的对象
*/
publicvoidsetObject(Objectobj[])throwsSQLException{
this.obj=obj;
}
publicvoidsetCountSql(Stringsql){
this.countSQL=sql;
}
/**
*执行查询取得一页数据,执行结束后关闭数据库连接
*@returnRowSetPage
.*@throwsSQLException
*/
publicPageexecuteQuery()throwsClassNotFoundException{
System.out.println("executeQueryUsingPreparedStatement");
DBUtilDBean=newDBUtil();
try{
objCount=DBean.doSearch(this.countSQL,obj);
if(!objCount.equals(null)){
System.out.println("thecountis="+objCount[0][0].toString());
totalCount=Integer.parseInt(objCount[0][0].toString());
System.out.println("thecountis="+totalCount);
}else{
totalCount=0;
}
if(totalCount<1)
returnnull;
objTables=DBean.doSearch(this.querySQL,obj);
this.setPage=newPage(this.objTables,startIndex,totalCount,pageSize);
returnthis.setPage;
}catch(SQLExceptionsqle){
//System.out.println("executeQuerySQLException");
sqle.printStackTrace();
}catch(Exceptione){
e.printStackTrace();
thrownewRuntimeException(e.toString());
}
returnnull;
}
/**
*取封装成Page的查询结果
*@returnPage
*/
publicPagegetPage(){
returnthis.setPage;
}
}
接下来是Action里面的代码,暂时定义这个Action是ComputerAction,代码如下:
packagecom.action;
importorg.apache.struts.action.Action;
importorg.apache.struts.action.ActionForward;
importorg.apache.struts.action.ActionMapping;
importorg.apache.struts.action.ActionForm;
importorg.apache.commons.logging.Log;
importorg.apache.commons.logging.LogFactory;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjavax.servlet.http.HttpSession;
importcom.form.LoginForm;
importcom.util.LoginBean;
importcom.util.ComputerBean;
importcom.util.BaseView;
importcom.util.Page;
importcom.model.FunctionManager;
importcom.attribute.Constants;
importcom.attribute.SQLBook;
importjava.sql.ResultSet;
/**
*CreatedbyIntelliJIDEA.
*User:7612CE
*Date:2005-6-14
*Time:13:31:34
*TochangethistemplateuseOptions|FileTemplates.
*/
publicclassComputerActionextendsBaseAction{
privateLoglog=LogFactory.getLog(this.getClass().getName());
publicActionForwardexecute(ActionMappingmapping,
ActionFormForm,
HttpServletRequestrequest,
HttpServletResponseresponse){
booleanflag=false;
Object[][]obj=null;
Pagepage=newPage();
Integerid=newInteger(Constants.id);
Stringsql=SQLBook.Computer_select_SQL;
BaseViewview=newBaseView();
StringpageNo=request.getParameter("pageNo");
if(pageNo==null||pageNo.equals("null")||pageNo.length()<=0){
pageNo="1";
}
try{
Object[]table={id};
ComputerBeancomputerBean=newComputerBean();
computerBean.setBeanDate(sql,table);
computerBean.setPageNo(pageNo);
page=computerBean.getResult();
obj=page.getData();
if(!obj.equals(null)){
flag=true;
view.setObject(obj);
request.setAttribute(Constants.QUERY_RESULT,view);
request.setAttribute("page",page);
}
}catch(Exceptionex){
ex.printStackTrace();
}
log.info("systemprinttheflag="+flag);
if(flag){
return(mapping.findForward(Constants.FORWARD_SUCCESS));
}else{
return(mapping.findForward(Constants.FORWARD_FAILURE));
}
}
}
由于Action里面用到了查询的SQL语句,所有SQL语句写在一个特定的类中,这个类名定义为SQLBook,代码如下:
publicclassSQLBook{
publicSQLBook(){}
/**
*computersql
*/
publicstaticfinalStringComputer_select_SQL=
"selecta.id,a.bookname,a.bookclass,b.classname,"+
"a.author,a.publish,a.bookno,a.content,a.prince,a.amount,"+
"a.Leav_number,a.regtime,a.picturefrombooka,bookclassb"+
"wherea.Bookclass=b.Idanda.bookclass=?"+
"orderbya.Iddesc";
publicstaticfinalStringComputer_select_count_sql=
"selectcount(*)frombooka,bookclassb"+
"wherea.Bookclass=b.Idanda.bookclass=?"+
"orderbya.Iddesc";
}
到此为止,基本上分页的代码基本完成,为了使得分页的代码共用,我把他封装成了一个标签,这个方法是参考一个网友的文章上写的,在这里表示感谢。这个自定义的标签命名为PaginatorTag,代码如下:
packagecom.util;
importjava.io.IOException;
importjavax.servlet.jsp.JspException;
importjavax.servlet.jsp.tagext.BodyTagSupport;
importorg.apache.commons.logging.Log;
importorg.apache.commons.logging.LogFactory;
publicclassPaginatorTagextendsBodyTagSupport{
protectedLoglog=LogFactory.getLog(this.getClass());
//以下是一标签中的一些属性,后面有较详细的介绍
intcurrentPage=1;//当前页码
Stringurl="";//转向的地址
inttotalSize=0;//总的记录数
intperPage=20;//每页显示的记录数目
booleanshowTotal=true;//是否显示总数量
booleanshowAllPages=false;//是否显示总页码
StringstrUnit="";//计数单位
//得到当前页码
publicintgetCurrentPage(){
returncurrentPage;
}
//设置当前页码
publicvoidsetCurrentPage(intcurrentPage){
this.currentPage=currentPage;
}
//得到每页显示记录的数目
publicintgetMaxPerPage(){
returnperPage;
}
//设置每页显示的记录数目
publicvoidsetMaxPerPage(intperPage){
this.perPage=perPage;
}
//判断是否显示总的页码数目
publicbooleanisShowAllPages(){
returnshowAllPages;
}
//设置是否显示总的页码数目
publicvoidsetShowAllPages(booleanshowAllPages){
this.showAllPages=showAllPages;
}
//判断是否显示总的记录数目
publicbooleanisShowTotal(){
returnshowTotal;
}
//设置是否显示总的记录数目
publicvoidsetShowTotal(booleanshowTotal){
this.showTotal=showTotal;
}
//得到计数单位
publicStringgetStrUnit(){
returnstrUnit;
}
//设置计数单位
publicvoidsetStrUnit(StringstrUnit){
this.strUnit=strUnit;
}
//得到总的记录数目
publicintgetTotalPut(){
returntotalSize;
}
//设置总的记录数目
publicvoidsetTotalPut(inttotalSize){
this.totalSize=totalSize;
}
//得到转向的链接地址
publicStringgetUrl(){
returnurl;
}
//设置链接地址
publicvoidsetUrl(Stringurl){
this.url=url;
}
publicintdoStartTag()throwsJspException{
returnSKIP_BODY;
}
publicintdoEndTag()throwsJspException{
Stringout=showPage(currentPage,url,totalSize,perPage,showTotal,showAllPages,strUnit);
try{
pageContext.getOut().print(out);
}catch(IOExceptione){
e.printStackTrace();
}
returnEVAL_PAGE;
}
/**
*作用:显示“上一页下一页”等信息
*
*@paramurl
*----链接地址
*@paramtotalSize
*----总数量
*@paramperPage
*----每页数量
*@paramshowTotal
*----是否显示总数量
*@paramshowAllPages
*---是否用下拉列表显示所有页面以供跳转。有某些页面不能使用,否则会出现JS错误。
*@paramstrUnit
*----计数单位
*@return.
*@throwsIOException
*/
protectedStringshowPage(intcurrentPage,Stringurl,inttotalSize,intperPage,
booleanshowTotal,booleanshowAllPages,StringstrUnit){
intn=0;
StringBufferbuf=newStringBuffer();
StringstrUrl;
n=(totalSize+perPage-1)/perPage;
buf.append("
if(showTotal==true)
buf.append("共"+totalSize+""+strUnit
+"");
strUrl=JoinChar(url);
if(currentPage<2){
buf.append("首页上一页");
}else{
buf.append("
buf.append("
+"/"+n+"页");
buf.append(""+perPage+""+strUnit+"/页");
if(showAllPages==true){
buf
.append("转到:
+strUrl
+"pageNo="
+"'+this.options[this.selectedIndex].value;\">");
for(inti=1;i<=n;i++){
buf.append("
if(currentPage==i)
buf.append("selected");
buf.append(">第"+i+"页 ");
}
buf.append("");
}
buf.append(" ");
return(buf.toString());
}
/**
*向地址中加入?或&
*@paramstrUrl
*----网址.
*@return加了?或&的网址.
*/
protectedStringJoinChar(StringstrUrl){
Stringresult="";
if(strUrl.equals("")||strUrl.length()<=0){
returnresult;
}
if(strUrl.indexOf("?")
if(strUrl.indexOf("?")>-1){
if(strUrl.indexOf("&")
result=strUrl+"&";
}else{
result=strUrl;
}
}else{
result=strUrl+"?";
}
}else{
result=strUrl;
}
returnresult;
}
}
有了自定义标签,当然少不了用于处理标签的tld,我们定义一个swsoft-struts.tld,代码如下:
有了自定义标签,当然少不了用于处理标签的tld,我们定义一个swsoft-struts.tld,代码如下:
好了,到现在我们开始来真正看一下jsp中的处理,我们写一个computer.jsp,代码如下:
<%@pageimport="com.util.baseview,
com.attribute.Constants,
com.util.Page"%>
<%@pagecontenttype="text/html;charset=gb2312"language="java"errorpage=""%>
<%@tagliburi="/web-inf/swsoft-struts.tld"prefix="swtag"%>
<%
StringcontextPath=request.getContextPath();
Stringurl=contextPath+"/computer.do";
BaseViewview=(BaseView)request.getAttribute(Constants.QUERY_RESULT);
Pagesetpage=(Page)request.getAttribute("page");
intcurrentPage=setpage.getCurrentPageNo();
System.out.println("thisiscurrentPage="+currentPage);
inttotalPut=setpage.getTotalSize();
System.out.println("thisistotalPut="+totalPut);
intmaxPerPage=setpage.getPageSize();
System.out.println("thisismaxPerPage="+maxPerPage);
if(view.haveRecord()){
StringsBgcolor="";
intlength=view.getRecordCount();
for(inti=0;i
Stringtype=view.getValue(i,2);
if(type.equals("1")){
type="computer";
}
if(i%2!=0){
sBgcolor="#A5C6EB";
}
else{
sBgcolor="#B7D7EF";
}
%>
<%}}%>
maxPerPage="<%=maxperpage%>"
showTotal="true"
showAllPages="true"
strUnit="页"/>
到此为止,分页的类基本完成,这样的话可以在别的模块都可以用这个标签,同时在开发别的系统的时候这个标签也可以使用,具有比较好的可移植性。这个数据库是mysql的,要是oracle的,仅仅在PageCtBean类中的intiQuerySQL方法里面改成
protectedStringintiQuerySQL(Stringsql,intstartIndex,intsize){
StringBufferquerySQL=newStringBuffer();
if(size!=this.MAX_PAGE_SIZE){
querySQL.append("select*from(selectmy_table.*,rownumasmy_rownumfrom(")
.append(sql)
.append(")my_tablewhererownum<").append(startIndex+size)
.append(")wheremy_rownum>=").append(startIndex);
}else{
querySQL.append("select*from(selectmy_table.*,rownumasmy_rownumfrom(")
.append(sql)
.append(")my_table")
.append(")wheremy_rownum>=").append(startIndex);
}
returnquerySQL.toString();
}
就可以了。
同时在数据库中,返回当前页需要显示的数据,主要有以下方法,。
a.使用mysql控制:
select*fromuser
orderbyHost
limitm,n
结果返回的是第m+1行到第n行的数据集。
比如
select*fromuser
orderbyHost
limit1,5
返回的是第2行到第5行的数据集
b.使用sqlserver
SELECT*
FROM(SELECTTOPm*
FROM(SELECTTOPn*
FROMCustomers)A
ORDERBYCustomerIDDESC)B
ORDERBYCustomerID
获得的结果集数据为第n-m+1行到第n行。
对整个过程的解释:
首先按照升序得到前n行的结果集A,然后按照降序从A中得到后m行的结果集B,最后按照升序对B进行重新排序,返回结果集。
其中CustomerID为主键
比如:
SELECT*
FROM(SELECTTOP5*
FROM(SELECTTOP10*
FROMCustomers)A
ORDERBYCustomerIDDESC)B
ORDERBYCustomerID
的意思就是返回包含第6行到第10行的数据结果集。
c.使用Oracle:
select*from(selectrownumr,*fromtest)tt
wherett.r>50andtt.r<=100;
希望大家有好方法的话能说一下,大家共同进步.