分页问题很普遍,开发者几乎都要用到,有关分页的原理可以参考前面的文章:
http://blog.csdn.net/treeroot/archive/2004/09/29/121071.aspx
很多人在分页问题上处理的不够好,往往导致整个程序混乱,代码可读性差,不过有经验的一般都会做一个JavaBean来实现分页功能,但是就是这个JavaBean如何设计又带来一大堆问题,有的甚至使程序逻辑更加混乱。这里推荐一篇文章:
http://blog.csdn.net/treeroot/articles/122802.aspx
设计这个分页功能要达到的目标:
1.和实现无关,这样才可以复用。
2.符合开闭原则,也就是说可以新增功能,但不需要修改。
3.简洁明了,容易看懂。
下面的设计每次只获取一页数据,每次都要重新设置查询总数,具体如何获得自己实现,这是一个比较通用的分页实现。
这里设计一个接口:
package treeroot.util;
import java.util.List;
/**
* 该接口用来实现分页功能,注意这里没有提供修改的功能。
* @author treerot
* @version 1.0
* @since 2004-9-30
*/
public interface Pageable
{
/**
* 获得数据结果
* @return
*/
public List getResult();
/**
* 获得查询总数
* @return
*/
public int getCount();
/**
* 获得每页纪录数
* @return
*/
public int getPageSize();
/**
* 获得当前页编号
* @return
*/
public int getCurrentPage();
/**
* 获得总页数
* @return
*/
public int getPages();
/**
* 每页默认显示纪录数
*/
public final static int DEFAULT_PAGESIZE=20;
}
这个接口非常简单,就是包括一个结果列表和一些分页的必要信息,这里注意几点:
1.这个接口的实现表示的是某一次查询的某一页数据,和上次查询无关
2.这个接口的实现应该是只读的,也就是说不可以修改的。
3.getPages()方法是冗余的,但是这里仍然提供这个方法。
下面给出一个抽象实现:
package treeroot.util;
import java.util.List;
/**
* @author treerot
* @version 1.0
* @since 2004-9-30
*/
public abstract class AbstractPage implements Pageable
{
private int currentPage;
private int pageSize;
private int pages;
protected int count;
protected List result;
/**
* 指定当前页
* @param currentPage
* @throws PageException
*/
public AbstractPage(int currentPage){
this(currentPage,Pageable.DEFAULT_PAGESIZE);
}
/**
* 指定当前页和页大小
* @param currentPage
* @param pageSize
* @throws PageException
*/
public AbstractPage(int currentPage,int pageSize) {
this.currentPage=currentPage;
this.pageSize=pageSize;
}
protected void checkPage(int currentPage) throws PageException{
if((currentPage<1)||(currentPage>this.getPages()))
throw new PageException("页超出范围:总页数为"+this.getPages()+",当前页为"+currentPage);
}
/**
* 这个方法被子类重写用来初始化,也就是计算count值和result结果,在子类 的构造函数中调用。
*/
abstract protected void init() throws PageException;
public List getResult()
{
return result;
}
public int getCount()
{
return count;
}
public int getPageSize()
{
return pageSize;
}
public int getCurrentPage()
{
return currentPage;
}
public int getPages()
{
if(pages==0) this.pages=(count+pageSize-1)/pageSize;
return pages;
}
}
这个抽象类实现了接口中的所有方法,但是定义了一个抽象方法init(),在子类中必须实现这个方法。上面的一个接口和一个抽象类看起来比较简单,你可能会觉得好像什么都没有做,实现上确实没有做什么,但是却可以给开发带来很大的帮助。我们可以根据自己的需要要继承这个抽象类,而数据可以通过各种方式获得,比如直接通过一个List获得,或者通过JDBC,Hibernate等等,不过我们都需要把结果封装到一个List里面,通过Hibernate就显得特别方便了。
PageException是自定义的一个异常
package treeroot.util
/**
* @author treeroot
* @version 1.0
* @since 2004-9-30
*/
public class PageException extends Exception
{
public PageException(){
super();
}
public PageException(String message){
super(message);
}
}
这里构建一个最简单的分页实现,也就是说通过查询结果的列表来构建页对象,这种情况是存在的:比如本次要显示的结果不是直接从数据库查询得到的,而是通过过滤某次数据库查询得到的,总之就是一个包含所有数据的结果集合。
不知道有没有说清楚,这里直接给出一个参考实现:
package treeroot.util;
import java.util.List;
/**
* @author treerot
* @version 1.0
* @since 2004-9-30
*/
public class ListPage extends AbstractPage implements Pageable
{
private List list;
/**
* 通过一个结果列表来初始化
* @param list
* @throws PageException
*/
public ListPage(List list) throws PageException
{
this(list, 1, Pageable.DEFAULT_PAGESIZE);
}
/**
* 通过一个结果列表来初始化,指定当前页
* @param list
* @param currentPage
* @throws PageException
*/
public ListPage(List list, int currentPage) throws PageException
{
this(list, currentPage, Pageable.DEFAULT_PAGESIZE);
}
/**
* 通过一个结果列表来初始化,指定当前页和页大小
* @param list
* @param currentPage
* @param pageSize
* @throws PageException
*/
public ListPage(List list, int currentPage, int pageSize) throws PageException
{
super(currentPage, pageSize);
//这里为了达到fail-fast,直接抛出异常,不是必须的。
if(list==null) throw new NullPointerException();
this.list = list;
init();
}
protected void init() throws PageException
{
this.count = list.size();
checkPage(this.getCurrentPage());
int fromIndex = (this.getCurrentPage() - 1) * this.getPageSize();
int toIndex = Math.min(fromIndex + this.getPageSize(), this.count);
this.result = list.subList(fromIndex, toIndex);
}
}
是不是觉得很简单,看一下怎么应用吧,假如你有一个方法就是获得查询结果,那么你就可以这样用了。
在Servlet或者Action中:
int currentPage=1;
String str=request.getParameter("currentPage");
if(str!=null){
try{
currentPage=Integer.parseInt(str);
}
catch(NumberFormatException e){}
}
List list= .... //这里获得所有结果,可能是直接获得数据库查询结果,也可能经过处理。
Pageable pg =null;
try{
pg= new ListPage(list,currentPage);
//或者通过Dao pg=(new Dao()).getResutl(currentPage); 返回类型为Pageable
}
catch(PageException e)
{
pg=null;
}
request.setAttribute("page",pg);
//转发给一个JSP页面显示。
是不是觉得很简洁?当然这里没有给出具体的的数据获取方法,但是在JSP显示页面就会觉得很舒服了,这里简单写一个JSP。
<%
Pageable pg=(Pageable)request.getAttribute("page");
%>
<table>
<tr>
<th>学号</th>
<th>姓名</th>
</tr>
<%
if(pg!=null){
List list=pg.getResult();
for(int i=0;i<list.size();i++){
Student stu=(Student)list.get(i);
%>
<tr>
<td><%=stu.getNumber()%></td>
<td><%=stu.getName()%></td>
</tr>
<%
}
%>
<tr>
<td colspan="2">
总纪录:<%=pg.getCount()%>
每页显示:<%=pg.getPageSize()%>  
页次:<%=pg.getCurrentPage()%>/<%=pg.getPages()%>
<a href="#" onClick="gotoPage(<%=pg.getCurrentPage()-1%>)">上一页</a>
<a href="#" onClick="gotoPage(<%=pg.getCurrentPage()+1%>)">上一页</a>
</td>
</tr>
<%
}
else{
%>
<tr>
<td colspan="2">指定的页不存在</td>
</tr>
<%
}
%>
这里只是简单得描述了一下,gotoPage是一个javascript函数,就是提交一个表单,指定当前页码。这里有些问题都没有处理:比如页码越界(可以在客户端也可以在服务器端验证,可以报错也可以自动纠正)。
我开始就是为了在Hibernate中使用分页才设计这个分页实现的,因为使用Hibernate时,查询后的结果被自动封装到一个List中了,所以使用起来特别方便,这里我做了一个比较庸俗的实现,就是查询参数只适合字符串类型,不过大部分查询还真的只是对字符串操作。
package treeroot.util;
import net.sf.hibernate.HibernateException;
import treeroot.common.dao.AbstractBaseDao;
/**
* @author treerot
* @version 1.0
* @since 2004-9-30
*/
public class HibernatePage extends AbstractPage implements Pageable
{
private String querySql;
private String countSql;
private String[] parameters;
public HibernatePage(int currentPage,String querySql,String countSql,String[] parameters) throws PageException{
this(currentPage,Pageable.DEFAULT_PAGESIZE,querySql,countSql,parameters);
}
public HibernatePage(int currentPage,int pageSize,String querySql,String countSql,String[] parameters) throws PageException
{
super(currentPage, pageSize);
this.querySql = querySql;
this.countSql = countSql;
this.parameters = parameters;
init();
}
protected void init() throws PageException
{
try{
this.count = AbstractBaseDao.queryCount(countSql);
int fromIndex = (this.getCurrentPage() - 1) * this.getPageSize();
int toIndex = Math.min(this.count, fromIndex + this.getPageSize());
this.result = AbstractBaseDao.find(this.querySql,this.parameters,fromIndex,toIndex);
}
catch (HibernateException e)
{
throw new PageException(e.getMessage());
}
}
}
这个类的设计并不是很合理的,因为查询只能接受字符串参数,但是如果只需要字符串参数就足够了。另外查询语句必须是JDBC风格的参数(?), 而不是Hibernate风格的(:=),你看过Dao里面的代码就知道为什么了。先看一下如何使用吧(一个Dao中的方法):
public Pageable findByName(String name,int currentPage) throws PageException{
String countSql="select count(*) from MyClass as c where c.name like ?";
String querySql="from MyClass as c where c.name like ?";
String[] parameter=new String[]{name};
return new HibernatePage(currentPage,countSql,querySql,parameter);
}
这个方法应该是比较简洁的,这里给出queryCount和find的实现,我对Hibernate的了解比较肤浅,所以下面的方法如果有什么不当的地方还望指出,谢谢!
public static int queryCount(String hql, String[] args) throws HibernateException
{
if (hql == null) throw new NullPointerException();
Object obj = null;
Transaction trans = null;
Session s = null;
try
{
s = HibernateSessionFactory.currentSession();
trans = s.beginTransaction();
Query q = s.createQuery(hql);
if (args != null)
{
for (int i = 0; i < args.length; i++){
q.setString(i, args[i]);
}
}
obj = q.uniqueResult();
trans.commit();
}
catch (HibernateException e)
{
if (trans != null)
{
try{
trans.rollback();
}
catch (HibernateException ex){//no need to care this Exception }
}
throw e;
}
return ((Integer) obj).intValue();
}
public static List find(String hql, String[] args, int fromIndex, int toIndex) throws HibernateException
{
if (hql == null) throw new NullPointerException();
List l = null;
Transaction trans = null;
Session s = null;
try{
s = HibernateSessionFactory.currentSession();
trans = s.beginTransaction();
Query q = s.createQuery(hql);
if (args != null){
for (int i = 0; i < args.length; i++){
q.setString(i, args[i]);
}
}
if (fromIndex > -1){
if (toIndex > fromIndex){
q.setFirstResult(fromIndex);
q.setMaxResults(toIndex - fromIndex);
}
else{
throw new IndexOutOfBoundsException();
}
}
l = q.list();
trans.commit();
}
catch (HibernateException e){
if (trans != null){
try{
trans.rollback();
}
catch (HibernateException ex){ //no need to care this Exception }
}
throw e;
}
return l;
}