技术开发 频道

通用分页实现及其OO设计探讨



    【IT168 专稿】分页是一种常用的页面数据显示技术,分页能够通过减少页面数据处理量从而提高了系统的性能。分页应该是做WEB开发必须掌握的一个小技术。而分页却是复杂的,倒不是它的技术有多复杂;而是有太多的重复代码,这些代码都难以重用。能不能实现一个通用的分页框架?每次只需要去覆写一两个方法,通过少量的代码就能实现分页的功能?

    一、一般分页应该要具有的功能有
    1. 灵活的设置分页大小。可以动态的设置分页大小,而不是写死到代码中。
    2. 自动计算总页数。根据分页大小和总记录数自动计算总页数。
    3. 获得当前页的页号。
    4. 获得当前页的总记录数。一般是最后一页的时候可能会小于分页大小。
    5. 判断当前页是否为第一页。
    6. 判断当前页是否为最后一页。
    7. 判断当前页是否有上一页。
    8. 判断当前页是否有下一页。
    9. 获得当前页的数据列表。
    10. 获得当前页的第一条记录的索引号
    11. 获得当前页的最后一条记录的索引号。

    二、常用的分页技术
    目前常用的分页技术有两种:
    1. 第一次访问是读取所有记录,放入session中,然后每次从session对象中读取当前页的数据
    2. 每次都访问数据库,从数据库中读取当前页的记录。
    这两种方法都各有优缺点,当数据量比较少时,第一种方法无疑是要快一些,因为减少与数据库的连接访问。而当数据量比较大时,比如查询结果可能会是上万条,那么内存的开销是十分大的,放到session中还有一个问题是能不能及时的清除无用的对象。而且这么大数据量在网络中传输也会使系统变得很慢。

    第二种方法就是专门解决这个问题的,它每次访问数据库,只读取当前页所需的记录,大大的减少网络传输量;它不会把页数据放到session中,大大提高服务器的性能。

    所以第二种方式要优于第一种方法。Session不要乱用,要用也仅仅是存放一些公共变量,相对于占用空间比较少的对象。不适合存放大量的数据,否则在很多个用户同时访问时那么系统会很慢,因为服务器内存被销耗的很厉害。


    三、通用分页框架需要解决的问题
    作为一个通用分页框架,
    1. 应该不依赖于任何其它框架
    2. 应该支持多种数据库
    3. 应该可以应用于任何web框架中,如:struts,spring等。
    4. 应该把数据访问的具体实现留给用户去实现。
    5. 应该实现关键的算法和过程,如:计算总页数,所需的实始化动作。
    6. 应该减化Contrller控制器的代码,以往的分页技术在Contrller中存在太多的if…else代码。十分难懂,应该由一个辅助类来实现。
    7. 应该减化jsp页面的代码,页面应该没有任何与分页相关的计算。应该由分页对象来实现。
    8. 应该支持两种分页方式,采用session或不采用session由用户控制。

    四、具体实现
    1.通用分页接口。定义接口可以有更多不同的实现,接口只声明了分页应该具有的公共行为。
    ViewPage.java
/** * 分页接口 * @author ex_yuanguangdong * */ public interface ViewPage { /** * 获取总页数 * @return 总页数 */ public int getPageCount(); /** * 获得页面大小 * @return 页面大小 */ public int getPageSize(); /** * 设置页面大小 * @param size */ public void setPageSize(int size); /** * 获得当前页数据 * @return 数据列表 */ public List getPageData(); /** * 获得当前页索引号 * @return 当前页索引号 */ public int getPageIndex(); /** * 获得当前页记录总数 * @return 当前页记录总数 */ public int getPageRows(); /** * 是否有下一页 * @return */ public boolean getHashNextPage(); /** * 是否有上一页 * @return */ public boolean getHashPreviousPage(); /** * 转到尾页 * */ public void gotoLastPage(); /** * 转到首页 * */ public void gotoFirstPage(); /** * 是否首页 * @return */ public boolean isFirstPage(); /** * 是否尾页 * @return */ public boolean isLastPage(); /** * 转到上一页 * */ public void gotoPreviousPage(); /** * 转到下一页 * */ public void gotoNextPage(); /** * 转到指定页面,pageIndex小于1时,转到第一页;pageIndex大于总页数时,转到最尾页 * @param pageIndex 指定的页号 */ public void gotoPage(int pageIndex); /** * 获取当前页第一条记录的记录号 * @return int 当前页第一条记录的记录号 */ public int getPageFirstRecord(); /** * 获取当前页最后一条记录的记录号 * @return int 当前页最后一条记录的记录号 */ public int getPageLastRecord(); }


    2. 分页抽像实现类,实现关键的算法

    AbstractViewPage.java
/** * 分页默认抽象实现 * 初始时,分页类有下列默认值: * 分页大小为-1,为不分页; * 总页数为1页 * 当前页为第一页 * 总记录数为0条 * 当前页数据列表为没有任何记录的列表 * @author ex_yuanguangdong * */ public abstract class AbstractViewPage implements ViewPage { //----------------------------------------- //私有静态常量 //----------------------------------------- private static final int DEFAULT_PAGE_INDEX = 1; private static final int DEFALT_PAGE_COUNT = 1; private static final int DEFAULT_PAGE_SIZE = -1; private static final int DEFAULT_ROWS = 0; //----------------------------------------- //私有成员变量 //----------------------------------------- /**当前页索引号**/ private int pageIndex = DEFAULT_PAGE_INDEX; /**总页数**/ private int pageCount =DEFALT_PAGE_COUNT; /**分页大小**/ private int pageSize = DEFAULT_PAGE_SIZE ; /**数据总记录数**/ private int rows = DEFAULT_ROWS; //------------------------------------------ //本地成员变量getter,setter方法 //------------------------------------------ /** * 设置新页号,只有大于等于1而且小于等于总页数并且不为当前页时,才允许设置 * @param pageIndex The pageIndex to set. */ private void setPageIndex(int newPageIndex) { if( newPageIndex >= this.DEFAULT_PAGE_INDEX && newPageIndex
<= this.getPageCount() && newPageIndex != this.pageIndex) { this.pageIndex = newPageIndex; } }
/** * @return Returns the rows. */ private int getRows() { return rows; } /** * @param rows The rows to set. */ private void setRows(int rows) { this.rows = rows; } /** * @param pageCount The pageCount to set. */ private void setPageCount(int pageCount) { this.pageCount = pageCount; } //-------------------------------------- //实现Page接口方法 //-------------------------------------- /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageData() */ public List getPageData() { List pageList =null; //获得当前页数据 pageList = this.pageList(this.getPageFirstRecord(), this.getPageRows()); //保证不返回null if(pageList == null) { pageList =new ArrayList(); } return pageList; } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageIndex() */ public int getPageIndex() { return this.pageIndex; } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#isFirstPage() */ public boolean isFirstPage() { return this.DEFAULT_PAGE_INDEX ==this.pageIndex; } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#isLastPage() */ public boolean isLastPage() { //当前页索引为总页数时为最后一页 return this.pageIndex == this.pageCount; } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getHashNextPage() */ public boolean getHashNextPage() { //当前页索引号小于总页数 return this.pageIndex < this.pageCount ; } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getHashPreviousPage() */ public boolean getHashPreviousPage() { //当前页索引号大于默认的初始页号,这里为1 return this.pageIndex > this.DEFAULT_PAGE_INDEX ; } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageCount() */ public int getPageCount() { return this.pageCount; } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageSize() */ public int getPageSize() { return this.pageSize; } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageRows() */ public int getPageRows() { //当页面大小为-1 时,返回总记录数 if(this.DEFAULT_PAGE_SIZE == this.pageSize ) { return this.rows; } //不为最后一页时,返回pageSize if(!this.isLastPage()) { return this.pageSize; } //最后一页时 return this.rows - (this.pageSize * (this.pageCount -1)); } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageFirstRecord() */ public int getPageFirstRecord() { //页大小为-1 时 if(this.DEFAULT_PAGE_SIZE== this.pageSize ) { return 0; } return (this.pageIndex -1)* this.pageSize; } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#getPageLastRecord() */ public int getPageLastRecord() { //页大小为-1时,返回总记录数 if(this.DEFAULT_PAGE_SIZE == this.pageSize) { return this.rows; } return this.getPageFirstRecord() + this.getPageRows() ; } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#gotoFirstPage() */ public void gotoFirstPage() { this.gotoPage(this.DEFAULT_PAGE_INDEX); } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#gotoLastPage() */ public void gotoLastPage() { this.gotoPage(this.getPageCount()); } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#gotoPreviousPage() */ public void gotoPreviousPage() { this.gotoPage(this.getPageIndex() -1); } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#gotoNextPage() */ public void gotoNextPage() { this.gotoPage(this.getPageIndex() + 1); } /** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#gotoPage(int) */ public void gotoPage(int newPageIndex) { if( newPageIndex >= this.DEFAULT_PAGE_INDEX && newPageIndex
<= this.getPageCount() ) { this.setPageIndex(newPageIndex); } }
/** * @see com.palic.elis.pos.junit.counter.web.mvc.ViewPage#setPageSize(int) */ public void setPageSize(int size) { if(size < 1) { size = 1; } this.pageSize = size; //进行初始化 this.doInit(); } //----------------------------------- //辅助方法 //----------------------------------- /** * 分页初始化方法,为了保证总是能正确的初始化,所以声明为final ,为了让子类可以调用声明为protected * */ protected final void doInit() { int rows = 0; //获得总记录数 rows= totalRows(); //设置总记录数 this.setRows(rows); //设置新的总页数 //计算并设置总页数 int pages = calculatePageCount(); this.setPageCount(pages); //转到第一页 this.gotoPage(this.DEFAULT_PAGE_INDEX); onInit(); } /** * 记算总页数 * @return 总页数 */ private int calculatePageCount() { //总记录数为0条,则返回的总页数为1 if(this.getRows() == 0) { return this.DEFALT_PAGE_COUNT; } //如果页面大小为-1,则返回的总页数为1 if(this.DEFAULT_PAGE_SIZE == this.getPageSize() ) { return this.DEFALT_PAGE_COUNT; } return this.getRows() / this.getPageSize() + ( this.getRows()
% this.getPageSize() ==0 ? 0 :1); }
/** * 获得总记录数,调用queryTotalRows(),将异常封装为non-checked 异常 * @return 总记录数 * @throws ApplicationRuntimeException */ private int totalRows() throws ApplicationRuntimeException{ try{ return queryTotalRows(); } catch(Exception ex){ throw new ApplicationRuntimeException(ex); } } /** * 获得当前页数据,调用queryPageList()方法,将异常封装为non-checked异常 * @param startRow 开始记录号 * @param rowCount 记录总数 * @return 当前页数据 * @throws ApplicationRuntimeException */ private List pageList(int startRow, int rowCount) throws ApplicationRuntimeException{ try{ return queryPageList(startRow, rowCount); }catch(Exception ex){ throw new ApplicationRuntimeException(ex); } } //----------------------------------------- //子类实现的方法 //----------------------------------------- /** * 初始化附加方法,由子类扩展 */ protected void onInit() { } /** * 查询获得总记录数,由子类具体实现 * @return 总记录数 * @throws Exception */ protected abstract int queryTotalRows() throws Exception; /** * 查询当前页数据,从startRow 开始的rowCount条记录 * @param startRow 开始记录号 * @param rowCount 记录总数 * @return 当前页数据 * @throws Exception */ protected abstract List queryPageList(int startRow, int rowCount) throws Exception; }


    3. 分页辅助类 
ViewPageHelper.java/** * 分页辅助类,用于减化Controller中的代码 * @author yuanguangdong * date: Oct 22, 2006 */ public class ViewPageHelper { private static final int FIRST_PAGE_VALUE = 1; private static final int PREVIOUS_PAGE_VALUE = 2; private static final int NEXT_PAGE_VALUE = 3; private static final int LAST_PAGE_VALUE = 4; private static final int SPECIAL_PAGE_VALUE = 5; public static final String FIRST_PAGE = "FIRST_PAGE"; public static final String PREVIOUS_PAGE = "PREVIOUS_PAGE"; public static final String NEXT_PAGE = "NEXT_PAGE"; public static final String LAST_PAGE = "LAST_PAGE"; public static final String SPECIAL_PAGE = "SPECIAL_PAGE"; /**分页动作参数名**/ public static final String PAGE_ACTION = "page_action"; /**分页对象属性名**/ public static final String SESSION_PAGE = "session_page"; /**页号参数名**/ public static final String PAGE_NO = "page_no"; private static Map actionMap = new HashMap(); static { actionMap.put(FIRST_PAGE, new Integer(FIRST_PAGE_VALUE)); actionMap.put(PREVIOUS_PAGE, new Integer(PREVIOUS_PAGE_VALUE)); actionMap.put(NEXT_PAGE, new Integer(NEXT_PAGE_VALUE)); actionMap.put(LAST_PAGE, new Integer(LAST_PAGE_VALUE)); actionMap.put(SPECIAL_PAGE, new Integer(SPECIAL_PAGE_VALUE)); } /** * 执行分页动作 * @param page 分页对象 * @param action 分页动作参数 * @param pageIndex 页号 */ public static void doAction(ViewPage page, String action, int pageIndex) { int actionIndex = 0; if (page == null) { throw new NullPointerException("Page对象null"); } if (action == null || "".equals(action)) { throw new IllegalArgumentException("无效的分页动作参数null"); } action = action.toUpperCase(); if (!actionMap.containsKey(action)) { throw new UnsupportedOperationException("不支持的分页动作参数:" + action); } Integer index = (Integer) actionMap.get(action); actionIndex = index.intValue(); switch (actionIndex) { case FIRST_PAGE_VALUE: page.gotoFirstPage(); break; case PREVIOUS_PAGE_VALUE: page.gotoPreviousPage(); break; case NEXT_PAGE_VALUE: page.gotoNextPage(); break; case LAST_PAGE_VALUE: page.gotoLastPage(); break; case SPECIAL_PAGE_VALUE: page.gotoPage(pageIndex); } } public static void doAction(ViewPage page, String action){ doAction(page, action, 1); } }

    五、应用通用分页框架
    1.继承AbstractViewPage类,实现queryPageList(int startRow, int endRow)和
    queryTotalRows()方法。

    protected int queryTotalRows() throws Exception
   获得查询条件的总记录数

    protected List queryPageList(int startRow, int rowCount)
    用于查询指定范围的数据。startRow为开始记录号, rowCount为查询的记录数

    queryPageList(0,20)为查询从第一条开始的20条记录。

   使用Ibatis可以由queryPageList调用queryForList()方法。

/** * 用户信息分页内部类 * @author yuanguangdong * date: Oct 22, 2006 */ class UserInfoPage extends AbstractViewPage{ //------------------------------------------------ //实现AbstractViewPage抽象类的抽象方法 //------------------------------------------------ /** * @see com.prs.application.ehld.web.mvc.AbstractViewPage#getPageDate(int, int) */ protected List queryPageList(int startRow, int endRow) throws Exception { return sampleAction.getUserInfoList(startRow, endRow); } /** * @see com.prs.application.ehld.web.mvc.AbstractViewPage#getRows() */ protected int queryTotalRows() throws Exception { return sampleAction.getUserCount(); } } 3. 在Contrller中的实现 public ModelAndView listUser(HttpServletRequest request, HttpServletResponse response) throws Exception { String pageAction = RequestUtils.getStringParameter(request,ViewPageHelper.PAGE_ACTION); Integer pageIndex = RequestUtils.getIntParameter(request,ViewPageHelper.PAGE_NO); //声明分页对象 ViewPage userPage = (ViewPage) request.getSession().getAttribute(ViewPageHelper.SESSION_PAGE); //第一次请求 if(pageAction == null || userPage == null){ //构建一个新的分页对象 userPage = new UserInfoPage(); //设置分页大小 userPage.setPageSize(2); }else{ if(ViewPageHelper.SPECIAL_PAGE.equals(pageAction)){ //如果页数为空,则默认为1 if (pageIndex == null) pageIndex = new Integer(1); ViewPageHelper.doAction(userPage,pageAction,pageIndex.intValue()); }else{ ViewPageHelper.doAction(userPage,pageAction); } } //从分页对象中获得当前页数据 List userInfoList = userPage.getPageData(); ModelAndView mav = new ModelAndView(userInfoListView); mav.addObject(this.userInfoListKey,userInfoList); request.getSession().setAttribute(ViewPageHelper.SESSION_PAGE,userPage); return mav; }


    4. jsp页面实现

<%@ page contentType="text/html;charset=utf-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %> <%@ taglib prefix="tiles" uri="http://jakarta.apache.org/struts/tags-tiles" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> <html> <head> <title>显示所有员工</title> <SCRIPT language="javaScript"> function pageNoChange(pageNo){ location.href= "ehld.sample.getuserinfolist.do?page_action=SPECIAL_PAGE&page_no="+pageNo.value; } </SCRIPT> </head> <body> <table width="80%" border="0"> <tr> <td bgcolor="#F0FEFF"><div align="left">&nbsp; 用户列表</div></td> </tr> </table> <br> <input name="adduser" type="submit" id="adduser" value="新增用户" onclick="location.href='ehld.sample.edituserinfo.do'"> <table width="80%" border="0"> <tr bgcolor="#58ED64"> <th width="25%">id</th> <th width="34%">姓名</th> <th colspan="2">操作</th> </tr> <c:forEach items="${userInfoList}" var="userInfoDTO"> <tr bgcolor="#D6EBF8"> <td><c:out value="${userInfoDTO.userID}"/></td> <td><c:out value="${userInfoDTO.userName}"/></td> <td width="21%"><a href="ehld.sample.edituserinfo.do?id=<c:out value='${userInfoDTO.userID}'/>">编辑</a></td> <td width="20%"><a href="#">删除</a></td> </tr> </c:forEach> </table> <c:if test="${session_page.firstPage}"> 首页 </c:if> <c:if test="${! session_page.firstPage}"> <a href="ehld.sample.getuserinfolist.do?page_action=FIRST_PAGE">首页</a> </c:if> <c:if test="${! session_page.hashPreviousPage}"> 上一页 </c:if> <c:if test="${session_page.hashPreviousPage}"> <a href="ehld.sample.getuserinfolist.do?page_action=PREVIOUS_PAGE">上一页</a> </c:if> <c:if test="${!session_page.hashNextPage}"> 下一页 </c:if> <c:if test="${session_page.hashNextPage}"> <a href="ehld.sample.getuserinfolist.do?page_action=NEXT_PAGE">下一页</a> </c:if> <c:if test="${session_page.lastPage}"> 尾页 </c:if> <c:if test="${!session_page.lastPage}"> <a href="ehld.sample.getuserinfolist.do?page_action=LAST_PAGE">尾页</a> </c:if> 共有<c:out value="${session_page.pageCount}" />页,第 <select name = "pageNo" onChange = "javaScript:pageNoChange(this);"> <c:forEach begin="1" end = "${session_page.pageCount}" var = "pageIndex"> <option value="<c:out value='${pageIndex}'/>" <c:if test = "${pageIndex ==session_page.pageIndex }">selected</c:if>> <c:out value="${pageIndex}"/> </option> </c:forEach> </select> </body> </html>

0
相关文章