改進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接口。

[java] view plaincopy
  1. import java.io.Serializable;  
  2. import java.util.List;  
  3. /** 
  4.  * 
  5.  * @author Sarkuya 
  6.  */  
  7. public interface PagedListHolder extends Serializable {  
  8.     public static final int DEFAULT_PAGE_SIZE = 10;  
  9.     public static final int DEFAULT_MAX_LINKED_PAGES = 10;  
  10.       
  11.     public void setRecordsSubst(List recordsSubset);  
  12.     public void setRealRecordCount(long realRecordCount);  
  13.       
  14.     /** 
  15.      * 設置每頁應有多少條記錄。 
  16.      */  
  17.     public void setPageSize(int pageSize);  
  18.       
  19.     /** 
  20.      * 返回每頁共有多少條記錄 
  21.      */  
  22.     public int getPageSize();  
  23.       
  24.     /** 
  25.      * 根據pageSize,返回共有多少頁 
  26.      */  
  27.     public int getPageCount();  
  28.       
  29.     /** 
  30.      * 返回當前頁碼。 
  31.      * 首頁爲0 
  32.      */  
  33.     public int getPage();  
  34.       
  35.     /** 
  36.      * 設置當前頁碼。 
  37.      * 首頁爲0 
  38.      */  
  39.     public void setPage(int page);  
  40.       
  41.     /** 
  42.      * 設置圍繞當前頁最多可以顯示多少鏈接的頁數。 
  43.      * 此方法<strong>會</strong>影響getFirstLinkedPage()及getLastLinkedPage() 
  44.      */  
  45.     public void setMaxLinkedPages(int maxLinkedPages);  
  46.       
  47.     /** 
  48.      * 返回圍繞當前頁最多可以顯示多少鏈接的頁數 
  49.      */  
  50.     public int getMaxLinkedPages();  
  51.       
  52.     /** 
  53.      * 返回首頁的頁碼(來源 www.iocblog.net) 
  54.      */  
  55.     public int getFirstLinkedPage();  
  56.       
  57.     /** 
  58.      * 返回最後一頁的頁碼 
  59.      */  
  60.     public int getLastLinkedPage();  
  61.       
  62.       
  63.     /** 
  64.      * 轉至前一頁。 
  65.      * 如果已經是首頁,則停在該頁。 
  66.      */  
  67.     public void previousPage();  
  68.       
  69.     /** 
  70.      * 轉至下一頁。 
  71.      * 如果已經是最後一頁,則停在該頁。 
  72.      */  
  73.     public void nextPage();  
  74.       
  75.     /** 
  76.      * 轉至首頁。 
  77.      */  
  78.     public void firstPage();  
  79.       
  80.     /** 
  81.      * 轉至最後一頁 
  82.      */  
  83.     public void lastPage();  
  84.       
  85.     /** 
  86.      * 返回總的記錄數 
  87.      */  
  88.     public long getNrOfElements();  
  89.       
  90.     /** 
  91.      * 返回在當前頁面上的第一個記錄在所有記錄(從0開始)中的編號 
  92.      */  
  93.     public int getFirstElementOnPage();  
  94.       
  95.     /** 
  96.      * 返回在當前頁面上的最後一個記錄在所有記錄(從0開始)中的編號 
  97.      */  
  98.     public int getLastElementOnPage();  
  99.       
  100.     /** 
  101.      * 返回在當前頁面上的所有記錄 
  102.      */  
  103.     public List getPageList();  
  104. }  

setRecordsSubst()用於存放頁面顯示的記錄源,而setRealRecordCount()用於記錄滿足條件的記錄總數。

下面是此接口的實現:

[java] view plaincopy
  1. import java.util.List;  
  2. import javax.servlet.http.HttpServletRequest;  
  3. import org.springframework.web.bind.ServletRequestDataBinder;  
  4. /** 
  5.  * 
  6.  * @author Sarkuya 
  7.  */  
  8. public class RequestPagedListHolder implements PagedListHolder {  
  9.     private static int pageSize = DEFAULT_PAGE_SIZE;  
  10.     private static int maxLinkedPages = DEFAULT_MAX_LINKED_PAGES;  
  11.       
  12.     private int page = 0;  
  13.     private List recordsSubset;  
  14.       
  15.     private long realRecordCount;  
  16.       
  17.     /** Creates a new instance of RequestPagedListHolder */  
  18.     public RequestPagedListHolder(HttpServletRequest request, long realRecordCount, PagedListProvider pagedListProvider) {  
  19.         setRealRecordCount(realRecordCount);  
  20.           
  21.         ServletRequestDataBinder binder = new ServletRequestDataBinder(this);  
  22.         binder.bind(request);  
  23.           
  24.         checkPageNavigation(request);  
  25.           
  26.         setRecordsSubst(pagedListProvider.getRecordsSubset(getPageSize() * getPage(), getPageSize()));  
  27.     }  
  28.     private void checkPageNavigation(final HttpServletRequest request) {  
  29.         String pageNavAction = request.getParameter("pageNavAction");  
  30.         if (pageNavAction != null) {  
  31.             if (pageNavAction.equals("firstPage")) {  
  32.                 firstPage();  
  33.             } else if (pageNavAction.equals("previousPage")) {  
  34.                 previousPage();  
  35.             } else if (pageNavAction.equals("nextPage")) {  
  36.                 nextPage();  
  37.             } else if (pageNavAction.equals("lastPage")) {  
  38.                 lastPage();  
  39.             }  
  40.         }  
  41.     }  
  42.       
  43.     public void setRecordsSubst(List recordsSubset) {  
  44.         this.recordsSubset = recordsSubset;  
  45.     }  
  46.       
  47.     public void setRealRecordCount(long realRecordCount) {  
  48.         this.realRecordCount = realRecordCount;  
  49.     }  
  50.       
  51.     public void setPageSize(int pageSize) {  
  52.         this.pageSize = pageSize;  
  53.     }  
  54.       
  55.     public int getPageSize() {  
  56.         return pageSize;  
  57.     }  
  58.       
  59.     public int getPageCount() {  
  60.         float nrOfPages = (float) getNrOfElements() / getPageSize();  
  61.         return (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1 : nrOfPages);  
  62.     }  
  63.       
  64.     public int getPage() {  
  65.         if (page >= getPageCount()) {  
  66.             page = getPageCount() - 1;  
  67.         }  
  68.         return page;  
  69.     }  
  70.       
  71.     public void setPage(int page) {  
  72.         this.page = page;  
  73.     }  
  74.       
  75.     public void setMaxLinkedPages(int maxLinkedPages) {  
  76.         this.maxLinkedPages = maxLinkedPages;  
  77.     }  
  78.       
  79.     public int getMaxLinkedPages() {  
  80.         return maxLinkedPages;  
  81.     }  
  82.       
  83.     public int getFirstLinkedPage() {  
  84.         return Math.max(0, getPage() - (getMaxLinkedPages() / 2));  
  85.     }  
  86.       
  87.     public int getLastLinkedPage() {  
  88.         return Math.min(getFirstLinkedPage() + getMaxLinkedPages() - 1, getPageCount() - 1);  
  89.     }  
  90.       
  91.     public void previousPage() {  
  92.         if (!isAtFirstPage()) {  
  93.             page--;  
  94.         }  
  95.     }  
  96.       
  97.     public void nextPage() {  
  98.         if (!isAtLastPage()) {  
  99.             page++;  
  100.         }  
  101.     }  
  102.       
  103.     public void firstPage() {  
  104.         setPage(0);  
  105.     }  
  106.       
  107.     public void lastPage() {  
  108.         setPage(getPageCount() - 1);  
  109.     }  
  110.       
  111.     public long getNrOfElements() {  
  112.         return realRecordCount;  
  113.     }  
  114.       
  115.     public int getFirstElementOnPage() {  
  116.         return (getPageSize() * getPage());  
  117.     }  
  118.       
  119.     public int getLastElementOnPage() {  
  120.         int endIndex = getPageSize() * (getPage() + 1);  
  121.         return (endIndex > getNrOfElements() ? (int)getNrOfElements() : endIndex) - 1;  
  122.     }  
  123.       
  124.     public List getPageList() {  
  125.         return recordsSubset;  
  126.     }  
  127.       
  128.     public boolean isAtFirstPage() {  
  129.         return getPage() == 0;  
  130.     }  
  131.       
  132.     public boolean isAtLastPage() {  
  133.         return getPage() == getPageCount() - 1;  
  134.     }  
  135. }  


此類有以下特點:

1. pageSize及maxLinkedPages均設爲static,這樣不因每個Request而改變。因此用戶不必每次顯示一個不同的頁面後都在UI中重新設置它們。
2. 在構造函數中包裝了所有的使用過程,既簡化了該類的使用,也保證了該類被正確初始化。
3. 摒棄了newPageSet變量,減少了各個方法的耦合強度。
4. 在Spring環境中使用了ServletRequestDataBinder,大大簡化了各個參數的讀取設置過程。
5. 通過回調機制,每次只檢索PagedListProvider所提供的記錄子集,節約了內存,提高了程序效率。

不難看出,PagedListProvider是個接口,只有一個方法:

[java] view plaincopy
  1. import java.util.List;  
  2. /** 
  3.  * 
  4.  * @author Sarkuya 
  5.  */  
  6. public interface PagedListProvider {  
  7.     public List getRecordsSubset(int firstResult, int maxResults);  
  8. }  

熟悉Hibernate的用戶知道,Hibernate中就是需要這兩個參數來實現分頁了。如果不使用Hibernate,也沒關係,自己實現此接口就行了。(接口實現起來很簡單,但技術細節卻非簡單,Hibernate用戶在此居於明顯的優勢)(來源 www.iocblog.net)

以上的兩個接口,一個實現類,便是經過改進後的分頁技術了。下面看其使用方法。

當用戶需要查看帶有分面功能的頁面時,都會由下面的方法處理:

[java] view plaincopy
  1. private void setPageListForView(HttpServletRequest request, ModelAndView mav, final String tableName) {  
  2.         long totalRecordsCount = adminService.getTotalRecordCount(tableName);  
  3.         PagedListProvider listProvider = new PagedListProvider() {  
  4.             public List getRecordsSubset(int firstResult, int maxResults) {  
  5.                 return (List) adminService.listTable(tableName, firstResult, maxResults);  
  6.             }  
  7.         };  
  8.           
  9.         PagedListHolder holder = new RequestPagedListHolder(request, totalRecordsCount, listProvider);  
  10.           
  11.         mav.addObject("pagedRecords", holder);  
  12.     }<span style="background-color: rgb(249, 252, 254); font-family: Tahoma, sans-serif;">    </span>  

這是一個簡單的方法,爲RequestPagedListHolder的構造函數準備好實參後,生成一個實例,將其置於頁面的一個名爲"pagedRecords"的attribute中,以供JSP讀取。

adminService.getTotalRecordCount()應不難實現。主要是getRecordsSubset()。Service層的listTable()如下:

[java] view plaincopy
  1. public Collection listTable(String tableName, int firstResult, int maxResults) throws DataAccessException {  
  2.     return ((HibernateDao) daoFactory.getDao(tableName)).findByCriteria(firstResult, maxResults);  
  3. }  

Dao層代碼:


[java] view plaincopy
  1. public Collection findByCriteria(int firstResult, int maxResults) throws DataAccessException {  
  2.        DetachedCriteria criteria = DetachedCriteria.forClass(getEntityClass());  
  3.        return getHibernateTemplate().findByCriteria(criteria, firstResult, maxResults);  
  4.    }  

下面看看視圖層的使用。

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

通過JSTL方便地讀出pagedRecords變量的pageList屬性。重抄一下上面的RequestPagedListHolder代碼相應部分:

    public List getPageList() {
        return recordsSubset;
    }

返回的正是Hibernate已經爲我們檢索出來的記錄子集。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章