posts - 1, comments - 1, trackbacks - 0, articles - 13
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          JSF中實現分頁(二)

          Posted on 2007-06-12 16:54 Linden.zhang 閱讀(176) 評論(0)  編輯  收藏 所屬分類: Jsf

            前面一篇直接使用了Myfaces中的兩個Component完成了一個簡單的分頁,這里將會介紹一種On-demand loading的方法來進行分頁,僅僅在需要數據的時候加載。

               先來說一些題外話,為了實現這種方式的分頁,公司里大約5-6個人做了半個多月的工作,擴展了dataTable,修改了dataScrollor,以及各種其他的方法,但是都不是很優雅。在上個月底的時候,在MyfacesMail List中也針對這個問題展開了一系列的討論,最后有人總結了討論中提出的比較好的方法,提出了以下的分頁方法,也是目前實現的最為優雅的方法,也就是不對dataTabledataScrollor做任何修改,僅僅通過擴展DataModel來實現分頁。

               DataModel 是一個抽象類,用于封裝各種類型的數據源和數據對象的訪問,JSFdataTable中綁定的數據實際上被包裝成了一個DataModel,以消除各種不同數據源和數據類型的復雜性,在前面一篇中我們訪問數據庫并拿到了一個List,交給dataTable,這時候,JSF會將這個List包裝成 ListDataModel dataTable訪問數據都是通過這個DataModel進行的,而不是直接使用List

               接下來我們要將需要的頁的數據封裝到一個DataPage中去,這個類表示了我們需要的一頁的數據,里面包含有三個元素:datasetSizestartRow,和一個用于表示具體數據的ListdatasetSize表示了這個記錄集的總條數,查詢數據的時候,使用同樣的條件取count即可,startRow表示該頁的起始行在數據庫中所有記錄集中的位置。

          /**
           * A simple class that represents a "page" of data out of a longer set, ie a
           * list of objects together with info to indicate the starting row and the full
           * size of the dataset. EJBs can return instances of this type when returning
           * subsets of available data.
           
          */

          public   class  DataPage
          {
              
          private   int  datasetSize;
              
          private   int  startRow;
              
          private  List data;

              
          /**
               * Create an object representing a sublist of a dataset.
               * 
               * 
          @param  datasetSize
               *            is the total number of matching rows available.
               * 
               * 
          @param  startRow
               *            is the index within the complete dataset of the first element
               *            in the data list.
               * 
               * 
          @param  data
               *            is a list of consecutive objects from the dataset.
               
          */

              
          public  DataPage( int  datasetSize,  int  startRow, List data)
              
          {
                  
          this .datasetSize  =  datasetSize;
                  
          this .startRow  =  startRow;
                  
          this .data  =  data;
              }


              
          /**
               * Return the number of items in the full dataset.
               
          */

              
          public   int  getDatasetSize()
              
          {
                  
          return  datasetSize;
              }


              
          /**
               * Return the offset within the full dataset of the first element in the
               * list held by this object.
               
          */

              
          public   int  getStartRow()
              
          {
                  
          return  startRow;
              }


              
          /**
               * Return the list of objects held by this object, which is a continuous
               * subset of the full dataset.
               
          */

              
          public  List getData()
              
          {
                  
          return  data;
              }

          }


           

               接下來,我們要對DataModel進行封裝,達到我們分頁的要求。該DataModel僅僅持有了一頁的數據DataPage,并在適當的時候加載數據,讀取我們需要頁的數據。


           

          /**
           * A special type of JSF DataModel to allow a datatable and datascroller to page
           * through a large set of data without having to hold the entire set of data in
           * memory at once.
           * <p>
           * Any time a managed bean wants to avoid holding an entire dataset, the managed
           * bean should declare an inner class which extends this class and implements
           * the fetchData method. This method is called as needed when the table requires
           * data that isn't available in the current data page held by this object.
           * <p>
           * This does require the managed bean (and in general the business method that
           * the managed bean uses) to provide the data wrapped in a DataPage object that
           * provides info on the full size of the dataset.
           
          */

          public   abstract   class  PagedListDataModel  extends  DataModel
          {
              
          int  pageSize;
              
          int  rowIndex;
              DataPage page;

              
          /**
               * Create a datamodel that pages through the data showing the specified
               * number of rows on each page.
               
          */

              
          public  PagedListDataModel( int  pageSize)
              
          {
                  
          super ();
                  
          this .pageSize  =  pageSize;
                  
          this .rowIndex  =   - 1 ;
                  
          this .page  =   null ;
              }


              
          /**
               * Not used in this class; data is fetched via a callback to the fetchData
               * method rather than by explicitly assigning a list.
               
          */


              
          public   void  setWrappedData(Object o)
              
          {
                  
          if (o  instanceof  DataPage)
                  
          {
                      
          this .page  =  (DataPage) o;
                  }

                  
          else
                  
          {
                      
          throw   new  UnsupportedOperationException( " setWrappedData " );
                  }

              }


              
          public   int  getRowIndex()
              
          {
                  
          return  rowIndex;
              }


              
          /**
               * Specify what the "current row" within the dataset is. Note that the
               * UIData component will repeatedly call this method followed by getRowData
               * to obtain the objects to render in the table.
               
          */


              
          public   void  setRowIndex( int  index)
              
          {
                  rowIndex 
          =  index;
              }


              
          /**
               * Return the total number of rows of data available (not just the number of
               * rows in the current page!).
               
          */


              
          public   int  getRowCount()
              
          {
                  
          return  getPage().getDatasetSize();
              }


              
          /**
               * Return a DataPage object; if one is not currently available then fetch
               * one. Note that this doesn't ensure that the datapage returned includes
               * the current rowIndex row; see getRowData.
               
          */

              
          private  DataPage getPage()
              
          {
                  
          if  (page  !=   null )
                  
          {
                      
          return  page;
                  }


                  
          int  rowIndex  =  getRowIndex();
                  
          int  startRow  =  rowIndex;
                  
          if  (rowIndex  ==   - 1 )
                  
          {
                      
          //  even when no row is selected, we still need a page
                      
          //  object so that we know the amount of data available.
                      startRow  =   0 ;
                  }


                  
          //  invoke method on enclosing class
                  page  =  fetchPage(startRow, pageSize);
                  
          return  page;
              }


              
          /**
               * Return the object corresponding to the current rowIndex. If the DataPage
               * object currently cached doesn't include that index then fetchPage is
               * called to retrieve the appropriate page.
               
          */


              
          public  Object getRowData()
              
          {
                  
          if  (rowIndex  <   0 )
                  
          {
                      
          throw   new  IllegalArgumentException(
                              
          " Invalid rowIndex for PagedListDataModel; not within page " );
                  }


                  
          //  ensure page exists; if rowIndex is beyond dataset size, then
                  
          //  we should still get back a DataPage object with the dataset size
                  
          //  in it
                   if  (page  ==   null )
                  
          {
                      page 
          =  fetchPage(rowIndex, pageSize);
                  }


                  
          int  datasetSize  =  page.getDatasetSize();
                  
          int  startRow  =  page.getStartRow();
                  
          int  nRows  =  page.getData().size();
                  
          int  endRow  =  startRow  +  nRows;

                  
          if  (rowIndex  >=  datasetSize)
                  
          {
                      
          throw   new  IllegalArgumentException( " Invalid rowIndex " );
                  }


                  
          if  (rowIndex  <  startRow)
                  
          {
                      page 
          =  fetchPage(rowIndex, pageSize);
                      startRow 
          =  page.getStartRow();
                  }

                  
          else   if  (rowIndex  >=  endRow)
                  
          {
                      page 
          =  fetchPage(rowIndex, pageSize);
                      startRow 
          =  page.getStartRow();
                  }

                  
          return  page.getData().get(rowIndex  -  startRow);
              }


              
          public  Object getWrappedData()
              
          {
                  
          return  page.getData();
              }


              
          /**
               * Return true if the rowIndex value is currently set to a value that
               * matches some element in the dataset. Note that it may match a row that is
               * not in the currently cached DataPage; if so then when getRowData is
               * called the required DataPage will be fetched by calling fetchData.
               
          */


              
          public   boolean  isRowAvailable()
              
          {
                  DataPage page 
          =  getPage();
                  
          if  (page  ==   null )
                  
          {
                      
          return   false ;
                  }


                  
          int  rowIndex  =  getRowIndex();
                  
          if  (rowIndex  <   0 )
                  
          {
                      
          return   false ;
                  }

                  
          else   if  (rowIndex  >=  page.getDatasetSize())
                  
          {
                      
          return   false ;
                  }

                  
          else
                  
          {
                      
          return   true ;
                  }

              }


              
          /**
               * Method which must be implemented in cooperation with the managed bean
               * class to fetch data on demand.
               
          */

              
          public   abstract  DataPage fetchPage( int  startRow,  int  pageSize);
              
          }


           

               最后,我們需要在Backing Bean中加一些東西,調用業務邏輯,并將數據交給PagedListDataModel,來幫我們完成最后的分頁工作。

             public  SomeManagedBean  {
              .


              
          private  DataPage getDataPage( int  startRow,  int  pageSize)  {
                
          //  access database here, or call EJB to do so
              }


              
          public  DataModel getDataModel()  {
                  
          if  (dataModel  ==   null {
                      dataModel 
          =   new  LocalDataModel(20);
                  }


                  
          return  dataModel;
              }


              
          private   class  LocalDataModel  extends  PagedListDataModel  {
                  
          public  LocalDataModel( int  pageSize)  {
                      
          super (pageSize);
                  }

                  
                  
          public  DataPage fetchPage( int  startRow,  int  pageSize)  {
                      
          //  call enclosing managed bean method to fetch the data
                       return  getDataPage(startRow, pageSize);
                  }

          }


          這里面有一個getDataPage的方法,只需要把所有業務邏輯的調用放在這里就可以了,最后業務邏輯調用的結果返回一個List,總條數返回一個int型的count放到DataPage中去就可以了。

          為了實現復用,把上面第三段的代碼中的LocalDataModel類和getDataPage方法抽到BasePagedBackingBean中,把getDataPage方法改成:

          protected abstract DataPage getDataPage(int startRow, int pageSize);

          這樣我們把所有需要分頁的Backing Bean繼承自這個抽象類,并實現getDataPage方法即可很容易的實現分頁。

           

             在具體應用中可以這么寫:

               protected  DataPage getDataPage( int  startRow,  int  pageSize)
              
          {
                  List scheduleList 
          =  scheduleService.getSchedulesByDate(scheduleDate, startRow, pageSize);
                  
          int  dataSetSize  =  scheduleService.getSchedulesCountByDate(scheduleDate);
                  
          return   new  DataPage(dataSetSize, startRow, scheduleList);
              }


          在數據訪問中,我們只需要取出我們需要行數的記錄就可以了,這在hibernate中非常容易實現。

          如果使用Criteria查詢的話,只要加上:

               criteria.setFirstResult(startRow);

               criteria.setMaxResults(pageSize);

          使用Query查詢的話,只要加上

               query.setFirstResult(startRow);

               query.setMaxResults(pageSize);

          并把兩個參數傳入即可。

          我們還需要另外寫一個CountDAO,取出相同查詢條件的記錄條數即可。

          還要修改一下Backing Bean中與dataTable綁定的property,將返回類型由List改成DataModel,而第一篇中用到的頁面不需要做任何修改就可以滿足新的需求了。

          里面最重要的是 PagedListDataModel fetchPage 這個方法,當滿足取數據的條件時,都會調用它取數據,因為業務邏輯不同,不便于將業務邏輯的調用放在里面實現,于是將其作為抽象方法,將具體的實現放到具體的Backing Bean中進行,在BaseBackingBean中,實現了這個方法,調用了getDataPage(startRow, pageSize)這個方法,而在BaseBackingBean中,這個方法又推遲到更具體的頁面中實現,這樣,我們在具體的頁面中只需要實現一個getDataPage(startRow, pageSize)這個方法訪問業務邏輯。

          大功告成,這個實現把前面遇到的兩個問題都解決了, On-demand loading 是沒有問題了,因為只有在首次讀取和換頁的時候DataModel才會向數據庫請求數據,雖然在JSF的生命周期中多次調用與dataTable綁定的方法,但是因為每次業務邏輯請求以后,數據都會存放在DataPage中,如果里面的數據滿足需求的話,就不再請求訪問數據庫,這樣多次訪問數據庫的問題也解決了。

          雖然這樣的話,dataScrollorTag使用起來還是很復雜,通常在同一個項目中,我們只會使用一種樣式的分頁導航,不過沒關系,我們只需要修改以下DataScrollorRender Kit,把一些可以定義的值固定下來,再定義一個TLD文件,就可以在項目中使用簡化版的Tag了。

          這個方法一開始發布在MyfacesWiki中,http://wiki.apache.org/myfaces/WorkingWithLargeTables,那里很少有人關注到,大家有興趣可以看看原文,本文只是對這種方法做一些簡單的介紹,并非自創,希望大家能夠多多關注開源社區,因為那里有最新最好的東西。

          Nightly Build服務器中拿到的12.27Myfaces包,發現里面擴充了很多新的Component,只是并沒有正式發布,大家有興趣的話可以研究研究。

          主站蜘蛛池模板: 阳江市| 东乌珠穆沁旗| 西昌市| 思茅市| 纳雍县| 桂平市| 原平市| 衡山县| 资中县| 河东区| 安泽县| 济源市| 阳山县| 奉节县| 陆良县| 城口县| 许昌县| 西峡县| 广东省| 南汇区| 浪卡子县| 太和县| 文水县| 田林县| 东至县| 施甸县| 武夷山市| 定兴县| 赤水市| 巴彦县| 修文县| 原平市| 乐安县| 阿坝县| 伊通| 泽州县| 海盐县| 雅安市| 聂拉木县| 景谷| 萍乡市|