改进Spring中的分页技术

Spring中有一个PagedListHolder,可以实现分页。但此类有几个缺点:

1. 使用此类的代码比较繁琐
2. 此类存放的数据源是所有的记录集,即对于记录数为1000条的数据,即使我们只需在一个页面中显示10条记录,每次均需要检索1000条记录出来,并且没有内在的缓存机制
3. 如果需将pageSize, maxLinkedPages这些一般为Session级的变量存于Session中,则必须在Session中存放PagedListHolder,从而导致大容量的数据常常撑满了Session
4. 只是实现了Serializable标识接口,且getPage(), setPage(), setPageSize()方法中直接使用newPageSet (private) 的属性,不利于子类覆盖。而且,内部类的各个方法耦合极强。特定方法的使用必须信赖于某个方法或标志变量作为前提条件。

比较理想的情况是,根据每一个HttpServletRequest产生一个PagesListHolder,不管记录总数有多少个,每次只检索页面上所显示的记录,但将pageSize, maxLinkedPages设为Session级的效果。

鉴于上述几点,我从Spring原有的PagedListHolder抽取出一些必需的方法名作为接口,并以一个名为RequestPagedListHolder的类实现之。

下面是抽取出来的PagedListHolder接口。

package com.sarkuya.web.pagination;

import java.io.Serializable;
import java.util.List;

/**
*
* @author Sarkuya
*/
public interface PagedListHolder extends Serializable {
public static final int DEFAULT_PAGE_SIZE = 10;
public static final int DEFAULT_MAX_LINKED_PAGES = 10;

public void setRecordsSubst(List recordsSubset);
public void setRealRecordCount(long realRecordCount);

/**
* 设置每页应有多少条记录。
*/
public void setPageSize(int pageSize);

/**
* 返回每页共有多少条记录
*/
public int getPageSize();

/**
* 根据pageSize,返回共有多少页
*/
public int getPageCount();

/**
* 返回当前页码。
* 首页为0
*/
public int getPage();

/**
* 设置当前页码。
* 首页为0
*/
public void setPage(int page);

/**
* 设置围绕当前页最多可以显示多少链接的页数。
* 此方法<strong>会</strong>影响getFirstLinkedPage()及getLastLinkedPage()
*/
public void setMaxLinkedPages(int maxLinkedPages);

/**
* 返回围绕当前页最多可以显示多少链接的页数
*/
public int getMaxLinkedPages();

/**
* 返回首页的页码
*/
public int getFirstLinkedPage();

/**
* 返回最后一页的页码
*/
public int getLastLinkedPage();


/**
* 转至前一页。
* 如果已经是首页,则停在该页。
*/
public void previousPage();

/**
* 转至下一页。
* 如果已经是最后一页,则停在该页。
*/
public void nextPage();

/**
* 转至首页。
*/
public void firstPage();

/**
* 转至最后一页
*/
public void lastPage();

/**
* 返回总的记录数
*/
public long getNrOfElements();

/**
* 返回在当前页面上的第一个记录在所有记录(从0开始)中的编号
*/
public int getFirstElementOnPage();

/**
* 返回在当前页面上的最后一个记录在所有记录(从0开始)中的编号
*/
public int getLastElementOnPage();

/**
* 返回在当前页面上的所有记录
*/
public List getPageList();
}

setRecordsSubst()用于存放页面显示的记录源,而setRealRecordCount()用于记录满足条件的记录总数。

下面是此接口的实现:

package com.sarkuya.web.pagination;

import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.ServletRequestDataBinder;

/**
*
* @author Sarkuya
*/
public class RequestPagedListHolder implements PagedListHolder {
private static int pageSize = DEFAULT_PAGE_SIZE;
private static int maxLinkedPages = DEFAULT_MAX_LINKED_PAGES;

private int page = 0;
private List recordsSubset;

private long realRecordCount;

/** Creates a new instance of RequestPagedListHolder */
public RequestPagedListHolder(HttpServletRequest request, long realRecordCount, PagedListProvider pagedListProvider) {
setRealRecordCount(realRecordCount);

ServletRequestDataBinder binder = new ServletRequestDataBinder(this);
binder.bind(request);

checkPageNavigation(request);

setRecordsSubst(pagedListProvider.getRecordsSubset(getPageSize() * getPage(), getPageSize()));
}

private void checkPageNavigation(final HttpServletRequest request) {
String pageNavAction = request.getParameter("pageNavAction");
if (pageNavAction != null) {
if (pageNavAction.equals("firstPage")) {
firstPage();
} else if (pageNavAction.equals("previousPage")) {
previousPage();
} else if (pageNavAction.equals("nextPage")) {
nextPage();
} else if (pageNavAction.equals("lastPage")) {
lastPage();
}
}
}

public void setRecordsSubst(List recordsSubset) {
this.recordsSubset = recordsSubset;
}

public void setRealRecordCount(long realRecordCount) {
this.realRecordCount = realRecordCount;
}

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

public int getPageSize() {
return pageSize;
}

public int getPageCount() {
float nrOfPages = (float) getNrOfElements() / getPageSize();
return (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1 : nrOfPages);
}

public int getPage() {
if (page >= getPageCount()) {
page = getPageCount() - 1;
}
return page;
}

public void setPage(int page) {
this.page = page;
}

public void setMaxLinkedPages(int maxLinkedPages) {
this.maxLinkedPages = maxLinkedPages;
}

public int getMaxLinkedPages() {
return maxLinkedPages;
}

public int getFirstLinkedPage() {
return Math.max(0, getPage() - (getMaxLinkedPages() / 2));
}

public int getLastLinkedPage() {
return Math.min(getFirstLinkedPage() + getMaxLinkedPages() - 1, getPageCount() - 1);
}

public void previousPage() {
if (!isAtFirstPage()) {
page--;
}
}

public void nextPage() {
if (!isAtLastPage()) {
page++;
}
}

public void firstPage() {
setPage(0);
}

public void lastPage() {
setPage(getPageCount() - 1);
}

public long getNrOfElements() {
return realRecordCount;
}

public int getFirstElementOnPage() {
return (getPageSize() * getPage());
}

public int getLastElementOnPage() {
int endIndex = getPageSize() * (getPage() + 1);
return (endIndex > getNrOfElements() ? (int)getNrOfElements() : endIndex) - 1;
}

public List getPageList() {
return recordsSubset;
}

public boolean isAtFirstPage() {
return getPage() == 0;
}

public boolean isAtLastPage() {
return getPage() == getPageCount() - 1;
}
}

此类有以下特点:

1. pageSize及maxLinkedPages均设为static,这样不因每个Request而改变。因此用户不必每次显示一个不同的页面后都在UI中重新设置它们。
2. 在构造函数中包装了所有的使用过程,既简化了该类的使用,也保证了该类被正确初始化。
3. 摒弃了newPageSet变量,减少了各个方法的耦合强度。
4. 在Spring环境中使用了ServletRequestDataBinder,大大简化了各个参数的读取设置过程。
5. 通过回调机制,每次只检索PagedListProvider所提供的记录子集,节约了内存,提高了程序效率。

不难看出,PagedListProvider是个接口,只有一个方法:

package com.sarkuya.web.pagination;

import java.util.List;

/**
*
* @author Sarkuya
*/
public interface PagedListProvider {
public List getRecordsSubset(int firstResult, int maxResults);
}

熟悉Hibernate的用户知道,Hibernate中就是需要这两个参数来实现分页了。如果不使用Hibernate,也没关系,自己实现此接口就行了。(接口实现起来很简单,但技术细节却非简单,Hibernate用户在此居于明显的优势)

以上的两个接口,一个实现类,便是经过改进后的分页技术了。下面看其使用方法。

当用户需要查看带有分面功能的页面时,都会由下面的方法处理:

private void setPageListForView(HttpServletRequest request, ModelAndView mav, final String tableName) {
long totalRecordsCount = adminService.getTotalRecordCount(tableName);

PagedListProvider listProvider = new PagedListProvider() {
public List getRecordsSubset(int firstResult, int maxResults) {
return (List) adminService.listTable(tableName, firstResult, maxResults);
}
};

PagedListHolder holder = new RequestPagedListHolder(request, totalRecordsCount, listProvider);

mav.addObject("pagedRecords", holder);
}

这是一个简单的方法,为RequestPagedListHolder的构造函数准备好实参后,生成一个实例,将其置于页面的一个名为"pagedRecords"的attribute中,以供JSP读取。

adminService.getTotalRecordCount()应不难实现。主要是getRecordsSubset()。Service层的listTable()如下:

public Collection listTable(String tableName, int firstResult, int maxResults) throws DataAccessException {
return ((HibernateDao) daoFactory.getDao(tableName)).findByCriteria(firstResult, maxResults);
}

Dao层代码:

public Collection findByCriteria(int firstResult, int maxResults) throws DataAccessException {
DetachedCriteria criteria = DetachedCriteria.forClass(getEntityClass());
return getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);
}

下面看看视图层的使用。

......
<c:forEach items="${pagedRecords.pageList}" var="record">
......
</c:forEach>
......

通过JSTL方便地读出pagedRecords变量的pageList属性。重抄一下上面的RequestPagedListHolder代码相应部分:

public List getPageList() {
return recordsSubset;
}

返回的正是Hibernate已经为我们检索出来的记录子集。

下面是图例(参考了NetBeans 5.5 Visual Web Pack的界面设计)。

图1. 数据库中共有8条记录,每页10行,一页全部显示完毕

图2. 设为每页5行,显示第0页。尽管共有8条记录,但实际上只检索了5条记录。

图3. 每页5行时的第1页,这次只检索了3条记录。且pageSize的值在同一个HttpServletSession中自动保存了下来。

经过这样改进后,代码耦合性大大减小了,也利于以后的扩展。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值