????
前面一篇直接使用了Myfaces中的兩個Component完成了一個簡單的分頁,這里將會介紹一種On-demand loading的方法來進行分頁,僅僅在需要數據的時候加載。
????
先來說一些題外話,為了實現這種方式的分頁,公司里大約5-6個人做了半個多月的工作,擴展了dataTable,修改了dataScrollor,以及各種其他的方法,但是都不是很優雅。在上個月底的時候,在Myfaces的Mail List中也針對這個問題展開了一系列的討論,最后有人總結了討論中提出的比較好的方法,提出了以下的分頁方法,也是目前實現的最為優雅的方法,也就是不對dataTable和dataScrollor做任何修改,僅僅通過擴展DataModel來實現分頁。
???? DataModel
是一個抽象類,用于封裝各種類型的數據源和數據對象的訪問,JSF中dataTable中綁定的數據實際上被包裝成了一個DataModel,以消除各種不同數據源和數據類型的復雜性,在前面一篇中我們訪問數據庫并拿到了一個List,交給dataTable,這時候,JSF會將這個List包裝成
ListDataModel
,dataTable訪問數據都是通過這個DataModel進行的,而不是直接使用List。
????
接下來我們要將需要的頁的數據封裝到一個DataPage中去,這個類表示了我們需要的一頁的數據,里面包含有三個元素:datasetSize,startRow,和一個用于表示具體數據的List。datasetSize表示了這個記錄集的總條數,查詢數據的時候,使用同樣的條件取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);
并把兩個參數傳入即可。
我們還需要另外寫一個Count的DAO,取出相同查詢條件的記錄條數即可。
還要修改一下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中,如果里面的數據滿足需求的話,就不再請求訪問數據庫,這樣多次訪問數據庫的問題也解決了。
雖然這樣的話,dataScrollor的Tag使用起來還是很復雜,通常在同一個項目中,我們只會使用一種樣式的分頁導航,不過沒關系,我們只需要修改以下DataScrollor的Render Kit,把一些可以定義的值固定下來,再定義一個TLD文件,就可以在項目中使用簡化版的Tag了。
這個方法一開始發布在Myfaces的Wiki中,http://wiki.apache.org/myfaces/WorkingWithLargeTables,那里很少有人關注到,大家有興趣可以看看原文,本文只是對這種方法做一些簡單的介紹,并非自創,希望大家能夠多多關注開源社區,因為那里有最新最好的東西。
從Nightly Build服務器中拿到的12.27的Myfaces包,發現里面擴充了很多新的Component,只是并沒有正式發布,大家有興趣的話可以研究研究。
posted on 2005-12-30 10:30
steady 閱讀(15014)
評論(21) 編輯 收藏 所屬分類:
JSF & Myfaces