一种分页解决方案

  开发MIS系统一个经常遇到的问题就是分页。这是一个经常出现而且没有太大变化的功能需求,我们完全可以编写一个可高度复用的解决方案。下面给出的就是一种基于Hibernate分页查询实现的一整套分页方案。在阐述这个方案之前,我想有必要就分页的相关问题做一个简单的阐述。

  一般来说分页有两种解决方法:一种是一次性查出所有符合条件的数据,只是在显示的时候选出一部分展示的页面上。这种做法的好处是实现起来简单,也不会频繁地访问的数据库,但是当查询出的数据量非常大而用户可能针对某一小部分数据感兴趣时,这种做法就变得十分低效了。另一种做法则是每次只查询出一页要显示的数据,每换一页,再重新查询一次。这种做法的好处是不会一次性读出所有数据,查询速度和查询效率都很高,但不足之处是需要频繁地访问数据库(事实上,由于连接池技术的存在这种频繁的访问也未必会真得会为数据库带来多大的负担)。应该说这两种方法各有优劣,要根据实际情况进行取舍。我个人更倾向于第二种解决方法,本文提供的解决方案也是基于这种方法实现的。

一、DAO层的分页组件

  DAO层的分页组件主要实现最低层的直接与数据库进行交互的分布查询功能。幸好Hibernate的API直接提供了对分页查询的支持:它们就是Query的setFirstResult方法(用于设置跳过的纪录数)和setMaxResults(用于设置一次要查出的纪录数)。一般来说分页是一个公共的需求,所以我们把分页方法写到BaseDAO里,代码如下:

public class BaseDAO
{
/**
*description:根据查询字符串进行分页查询,分页的位置由skipedRows和showedRows确定
*
*
@param queryString
*查询字符串
*
@param skipedRows
*路过的纪录数
*
@param showedRows
*要显示的纪录数
*
@return 分布查询的结果
*/
public ListPagingQueryByHql(StringqueryString, int skipedRows,
int showedRows)
{
Listresult
= null ;
Sessionsession
= HibernateSessionFactory.getCurrentSession();
QueryqueryObject
= session.createQuery(queryString).setFirstResult(
skipedRows).setMaxResults(showedRows);
result
= queryObject.list();
session.close();
return result;
}

/**
*description:根据查询字符串查询纪录总数。关于这里使用的实现方法有待进一步斟酌
*
*
@param queryString
*查询字符串
*
@return 纪录总数
*/
public int getQueryResultTotal(StringqueryString)
{
StringBufferqueryTotalString
= new StringBuffer( " selectcount(*) " );
queryTotalString.append(queryString.substring(queryString
.indexOf(
" from " )));
Sessionsession
= HibernateSessionFactory.getCurrentSession();
Integertotal
= (Integer)session.createQuery(
queryTotalString.toString()).uniqueResult();
return total.intValue();
}
}

  按照B/S开发的三层架构,我们的分页功能也被分散到DAO层、业务层和Web层这三个层次,每一层关注于特定的实现,并向上一层提供服务。下面就分这三个层次来逐步展示我们的解决方案。

  二、业务层的分页组件

  业务层的分页组件只是简单调用DAO组件的相关方法即可,它的代码如下,其中ParticularManager是服务层定义的某个接口,ParticularManagerImpl是它的一个实现类。

public class ParticularManagerImpl implements ParticularManager
{

private ParticularDAO; // 一个特定DAO继承自BaseDAO,它由spring依赖注入

/**
*description:获取查询结果,调用ParticularDAO的PagingQueryByHql方法实现分页查询业务
*
*
@param qo
*查询对象,携带查询关键字
*
@param skipedRows
*路过的纪录数
*
@param showedRows
*要显示的纪录数
*
@return 分页查询的结果
*/
public Listquery(QOqo, int skipedRows, int showedRows)
{
// ......
}

/**
*description:获取查询结果的记录总数,调用ParticularDAO的getQueryResultTotal方法实现分页查询业务
*
*
@param qo
*查询对象,携带查询关键字
*/
public int getRecordCount(QOqo)
{
// ......
}
}

三、Web层的分页组件

Web层的分页组件包含一个ParticularForm,和一个ParticularAction。它们的代码分别如下:

public class ParticularForm extends ActionForm
{
private static final long serialVersionUID = 1L ;

private QOqo; // 查询对象qo

private ListqueryResult; // 查询结果

private Stringfooter = "" ; // 页角字符串

private int targetPage = 1 ;  // 目标页

private int pageSize = 20 ;  // 页面大小

public void reset(ActionMappingmapping,HttpServletRequestrequest)
{
qo
= new QO();
queryResult
= null ;
}

/*
*以下为各字段的getter和setter
*/
public QOgetQo()
{
return qo;
}
public void setQo(QOqo)
{
this .qo = qo;
}
public ListgetQueryResult()
{
return queryResult;
}
public void setQueryResult(ListqueryResult)
{
this .queryResult = queryResult;
}

public StringgetFooter()
{
return footer;
}

public void setFooter(Stringfooter)
{
this .footer = footer;
}

public int getPageSize()
{
return pageSize;
}

public void setPageSize( int pageSize)
{
this .pageSize = pageSize;
}

public int getTargetPage()
{
return targetPage;
}

public void setTargetPage( int targetPage)
{
this .targetPage = targetPage;
}

}

public class ParticularAction extends BaseAction
{

private PagingUtilpagingUtil = new PagingUtil();  // 分页工具类

/**
*查询信息
*
@param mapping
*
@param form
*
@param request
*
@param response
*
@return
*/
public ActionForwardquery(ActionMappingmapping,ActionFormform,
HttpServletRequestrequest,HttpServletResponseresponse)
{
ParticularFormparticularForm
= (ParticularForm)form;
ParticularManagerparticularManager
= (ParticularManager)ServiceLocator.getService( " particularManager " );
StxxQueryQOqo
= stxxQueryForm.getQo();
if (qo == null )
return null ;
// 查询字符串
StringBufferqueryString = new StringBuffer( " fromBaseStxxbPOasstwhere1=1 " );
// 从qo中取出数据,组织查询字符串....
// ......
pagingUtil.setRecordTotal(particularManager.getQueryResultTotal(queryString.toString()));
pagingUtil.setPageSize(particularForm.getPageSize());
pagingUtil.setCurrentPage(particularForm.getTargetPage());
Listresult
= particularManager.pagingQueryByHql(queryString.toString(),pagingUtil.getSkippedRecordTotal(),   pagingUtil.getPageSize());
particularForm.setQueryResult(result);
// 将查询结果赋给ActionForm
particularForm.setFooter(pagingUtil.createSnippet());
return mapping.findForward( " QueryList " ); // 转向列表页面
}
}

四、分页工具类

上面的类中用到了一个PagingUtil类,这是这个分页方案的又一个核心。它的代码如下:

/**
*Description:分页工具类,实现数据分页显示
*/
public class PagingUtil
{

private int recordTotal; // 记录总数

private int pageSize; // 每页显示记录数

private int pageTotal; // 总页数

private int currentPage; // 当前页数

private boolean hasPreviousPage; // 是否有上一页

private boolean hasNextPage; // 是否有下一页

/**
*默认构造器,将页面大小初始化为20
*/
public PagingUtil()
{
this .recordTotal = 0 ;
this .pageSize = 20 ;
this .pageTotal = 1 ;
this .currentPage = 1 ;
this .hasPreviousPage = false ;
this .hasNextPage = false ;
}

/**
*根据记录总数和页面大小进行初始化
*
*
@param recordTotal
*记录总数
*
@param pageSize
*页面大小
*/
public PagingUtil( int recordTotal, int pageSize)
{
this .recordTotal = recordTotal;
this .pageSize = pageSize;
this .currentPage = 1 ;
this .resetPageTotal();
this .refresh();
}

/**
*获取已经跳过的记录总数
*
*
@return int跳过的记录总数
*/
public int getSkippedRecordTotal()
{
return (currentPage - 1 ) * pageSize;
}

/**
*刷新页面状态
*/
public void refresh()
{
if (pageTotal <= 1 )
{
hasPreviousPage
= false ;
hasNextPage
= false ;
}
else if (currentPage == 1 )
{
hasPreviousPage
= false ;
hasNextPage
= true ;
}
else if (currentPage == pageTotal)
{
hasPreviousPage
= true ;
hasNextPage
= false ;
}
else
{
hasPreviousPage
= true ;
hasNextPage
= true ;
}
}

/**
*生成页面上实现分页导航功能的JSP页面片断,以字符串形式记录.
*
*
@return String其内容表示实现分页导航功能的JSP页面片断,该字符串使用<bean:write>标签写到页面上时必须放置在一个form里
*/
public StringcreateSnippet()
{
StringBuffersnippet
= new StringBuffer( "" );

if (hasPreviousPage)
{
snippet.append(
" <INPUTtype=submitclass='btn'value=首页name=firsonclick='this.form.targetPage.value=1'> " );
snippet.append(
" <INPUTtype=submitclass='btn'value=上页name=prevonclick='this.form.targetPage.value= " + (currentPage - 1 ) + " '> " );
}
else
{
snippet.append(
" <INPUTtype=submitclass='btn'value=首页name=firsdisabled> " );
snippet.append(
" <INPUTtype=submitclass='btn'value=上页name=prevdisabled> " );
}

if (hasNextPage)
{
snippet.append(
" <INPUTtype=submitclass='btn'value=下页name=nextonclick='this.form.targetPage.value= " + (currentPage + 1 ) + " '> " );
snippet.append(
" <INPUTtype=submitclass='btn'value=末页name=lastonclick='this.form.targetPage.value= " + pageTotal + " '> " );
}
else
{
snippet.append(
" <INPUTtype=submitclass='btn'value=下页name=nextdisabled> " );
snippet.append(
" <INPUTtype=submitclass='btn'value=末页name=lastdisabled> " );
}

snippet.append(
" " + recordTotal + " 条记录 " );
snippet.append(
" 每页<SELECTsize=1name=pageSizeonchange='this.form.targetPage.value=1;this.form.pageSize.value=this.value;this.form.submit();'> " );

if (pageSize == 5 )
{
snippet.append(
" <OPTIONvalue=5selected>5</OPTION> " );
}
else
{
snippet.append(
" <OPTIONvalue=5>5</OPTION> " );
}

if (pageSize == 10 )
{
snippet.append(
" <OPTIONvalue=10selected>10</OPTION> " );
}
else
{
snippet.append(
" <OPTIONvalue=10>10</OPTION> " );
}
if (pageSize == 20 )
{
snippet.append(
" <OPTIONvalue=20selected>20</OPTION> " );
}
else
{
snippet.append(
" <OPTIONvalue=20>20</OPTION> " );
}
if (pageSize == 50 )
{
snippet.append(
" <OPTIONvalue=50selected>50</OPTION> " );
}
else
{
snippet.append(
" <OPTIONvalue=50>50</OPTION> " );
}
if (pageSize == 100 )
{
snippet.append(
" <OPTIONvalue=100selected>100</OPTION> " );
}
else
{
snippet.append(
" <OPTIONvalue=100>100</OPTION> " );
}
snippet.append(
" </SELECT> " );
snippet.append(
" 条分 " + pageTotal + " 页显示转到 " );
snippet.append(
" <SELECTsize=1name=Pagelistonchange='this.form.targetPage.value=this.value;this.form.submit();'> " );
for ( int i = 1 ;i < pageTotal + 1 ;i ++ )
{
if (i == currentPage)
{
snippet.append(
" <OPTIONvalue= " + i + " selected> " + i
+ " </OPTION> " );
}
else
{
snippet.append(
" <OPTIONvalue= " + i + " > " + i + " </OPTION> " );
}
}
snippet.append(
" </SELECT>页 " );
snippet.append(
" <INPUTtype=hiddenvalue= " + currentPage
+ " name="targetPage"> " );
snippet.append(
" <INPUTtype=hiddenvalue= " + pageSize
+ " name="pageSize"> " );
return snippet.toString();
}

/** 以下为getter和setter方法* */

/**
*获取记录总数
*
*
@return int总行数
*/
public int getRecordTotal()
{
return recordTotal;
}

/**
*设置记录总数,同时利用recordTotal和pageSize计算出pageTotal。
*
*
@param recordTotal记录总数
*/
public void setRecordTotal( int recordTotal)
{
this .recordTotal = recordTotal;
resetPageTotal();
refresh();
}

/**
*计算总页数
*
*
@return int总页面数
*/
public int getPageTotal()
{
return pageTotal;
}

/**
*重置页面总数
*/
protected void resetPageTotal()
{
if (pageSize >= 1 )
{
pageTotal
= (recordTotal + pageSize - 1 ) / pageSize;
}
else
{
pageTotal
= 1 ;
}
}

/**
*获取每页显示记录数
*
*
@return int每页显示的记录数
*/
public int getPageSize()
{
return pageSize;
}

/**
*
*设置每页显示记录数
*
*
@param pageSize
*每页显示记录数
*/
public void setPageSize( int pageSize)
{
this .pageSize = pageSize;
resetPageTotal();
refresh();
}

/**
*获取当前页数
*
*
@return int当前的页数
*/
public int getCurrentPage()
{
return currentPage;
}

/**
*设置当前页数
*
*
@return int当前的页数
*/
public void setCurrentPage( int currentPage)
{
if (currentPage >= 1 && currentPage <= pageTotal)
{
this .currentPage = currentPage;
refresh();
}
}

/**
*获取是否有下一页的状态信息
*
*
@return blooean是否有下一页
*/
public boolean isHasNextPage()
{
return hasNextPage;
}

/**
*获取是否有上一页的状态信息
*
*
@return blooean是否有上一页
*/
public boolean isHasPreviousPage()
{
return hasPreviousPage;
}
}

  需要特别说明的是createSnippet方法,它的作用就是根据当前页面情况动态的组织要写入到页面上的分页页面片段。而我们在页面上的工作就会因此而变得轻松了许多,因为这样一来,我们在页面上唯一要做的就是写下这一样一个语句:<bean:write name="particularForm" property="footer"/>

你可能感兴趣的:(解决方案)