????
前面一篇直接使用了Myfaces中的兩個(gè)Component完成了一個(gè)簡(jiǎn)單的分頁(yè),這里將會(huì)介紹一種On-demand loading的方法來(lái)進(jìn)行分頁(yè),僅僅在需要數(shù)據(jù)的時(shí)候加載。
????
先來(lái)說(shuō)一些題外話,為了實(shí)現(xiàn)這種方式的分頁(yè),公司里大約5-6個(gè)人做了半個(gè)多月的工作,擴(kuò)展了dataTable,修改了dataScrollor,以及各種其他的方法,但是都不是很優(yōu)雅。在上個(gè)月底的時(shí)候,在Myfaces的Mail List中也針對(duì)這個(gè)問(wèn)題展開(kāi)了一系列的討論,最后有人總結(jié)了討論中提出的比較好的方法,提出了以下的分頁(yè)方法,也是目前實(shí)現(xiàn)的最為優(yōu)雅的方法,也就是不對(duì)dataTable和dataScrollor做任何修改,僅僅通過(guò)擴(kuò)展DataModel來(lái)實(shí)現(xiàn)分頁(yè)。
???? DataModel
是一個(gè)抽象類(lèi),用于封裝各種類(lèi)型的數(shù)據(jù)源和數(shù)據(jù)對(duì)象的訪問(wèn),JSF中dataTable中綁定的數(shù)據(jù)實(shí)際上被包裝成了一個(gè)DataModel,以消除各種不同數(shù)據(jù)源和數(shù)據(jù)類(lèi)型的復(fù)雜性,在前面一篇中我們?cè)L問(wèn)數(shù)據(jù)庫(kù)并拿到了一個(gè)List,交給dataTable,這時(shí)候,JSF會(huì)將這個(gè)List包裝成
ListDataModel
,dataTable訪問(wèn)數(shù)據(jù)都是通過(guò)這個(gè)DataModel進(jìn)行的,而不是直接使用List。
????
接下來(lái)我們要將需要的頁(yè)的數(shù)據(jù)封裝到一個(gè)DataPage中去,這個(gè)類(lèi)表示了我們需要的一頁(yè)的數(shù)據(jù),里面包含有三個(gè)元素:datasetSize,startRow,和一個(gè)用于表示具體數(shù)據(jù)的List。datasetSize表示了這個(gè)記錄集的總條數(shù),查詢(xún)數(shù)據(jù)的時(shí)候,使用同樣的條件取count即可,startRow表示該頁(yè)的起始行在數(shù)據(jù)庫(kù)中所有記錄集中的位置。
/**?*/
/**
?*?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;
????}
}
????
接下來(lái),我們要對(duì)DataModel進(jìn)行封裝,達(dá)到我們分頁(yè)的要求。該DataModel僅僅持有了一頁(yè)的數(shù)據(jù)DataPage,并在適當(dāng)?shù)臅r(shí)候加載數(shù)據(jù),讀取我們需要頁(yè)的數(shù)據(jù)。
/**?*/
/**
?*?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中加一些東西,調(diào)用業(yè)務(wù)邏輯,并將數(shù)據(jù)交給PagedListDataModel,來(lái)幫我們完成最后的分頁(yè)工作。
??
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);
????????}
}
這里面有一個(gè)getDataPage的方法,只需要把所有業(yè)務(wù)邏輯的調(diào)用放在這里就可以了,最后業(yè)務(wù)邏輯調(diào)用的結(jié)果返回一個(gè)List,總條數(shù)返回一個(gè)int型的count放到DataPage中去就可以了。
為了實(shí)現(xiàn)復(fù)用,把上面第三段的代碼中的LocalDataModel類(lèi)和getDataPage方法抽到BasePagedBackingBean中,把getDataPage方法改成:
protected abstract DataPage getDataPage(int startRow, int pageSize);
這樣我們把所有需要分頁(yè)的Backing Bean繼承自這個(gè)抽象類(lèi),并實(shí)現(xiàn)getDataPage方法即可很容易的實(shí)現(xiàn)分頁(yè)。
?
???在具體應(yīng)用中可以這么寫(xiě):
????
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);
????}
在數(shù)據(jù)訪問(wèn)中,我們只需要取出我們需要行數(shù)的記錄就可以了,這在hibernate中非常容易實(shí)現(xiàn)。
如果使用Criteria查詢(xún)的話,只要加上:
???? criteria.setFirstResult(startRow);
???? criteria.setMaxResults(pageSize);
使用Query查詢(xún)的話,只要加上
???? query.setFirstResult(startRow);
???? query.setMaxResults(pageSize);
并把兩個(gè)參數(shù)傳入即可。
我們還需要另外寫(xiě)一個(gè)Count的DAO,取出相同查詢(xún)條件的記錄條數(shù)即可。
還要修改一下Backing Bean中與dataTable綁定的property,將返回類(lèi)型由List改成DataModel,而第一篇中用到的頁(yè)面不需要做任何修改就可以滿(mǎn)足新的需求了。
里面最重要的是 PagedListDataModel 中 fetchPage 這個(gè)方法,當(dāng)滿(mǎn)足取數(shù)據(jù)的條件時(shí),都會(huì)調(diào)用它取數(shù)據(jù),因?yàn)闃I(yè)務(wù)邏輯不同,不便于將業(yè)務(wù)邏輯的調(diào)用放在里面實(shí)現(xiàn),于是將其作為抽象方法,將具體的實(shí)現(xiàn)放到具體的Backing Bean中進(jìn)行,在BaseBackingBean中,實(shí)現(xiàn)了這個(gè)方法,調(diào)用了getDataPage(startRow, pageSize)這個(gè)方法,而在BaseBackingBean中,這個(gè)方法又推遲到更具體的頁(yè)面中實(shí)現(xiàn),這樣,我們?cè)诰唧w的頁(yè)面中只需要實(shí)現(xiàn)一個(gè)getDataPage(startRow, pageSize)這個(gè)方法訪問(wèn)業(yè)務(wù)邏輯。
大功告成,這個(gè)實(shí)現(xiàn)把前面遇到的兩個(gè)問(wèn)題都解決了,
On-demand loading
是沒(méi)有問(wèn)題了,因?yàn)橹挥性谑状巫x取和換頁(yè)的時(shí)候DataModel才會(huì)向數(shù)據(jù)庫(kù)請(qǐng)求數(shù)據(jù),雖然在JSF的生命周期中多次調(diào)用與dataTable綁定的方法,但是因?yàn)槊看螛I(yè)務(wù)邏輯請(qǐng)求以后,數(shù)據(jù)都會(huì)存放在DataPage中,如果里面的數(shù)據(jù)滿(mǎn)足需求的話,就不再請(qǐng)求訪問(wèn)數(shù)據(jù)庫(kù),這樣多次訪問(wèn)數(shù)據(jù)庫(kù)的問(wèn)題也解決了。
雖然這樣的話,dataScrollor的Tag使用起來(lái)還是很復(fù)雜,通常在同一個(gè)項(xiàng)目中,我們只會(huì)使用一種樣式的分頁(yè)導(dǎo)航,不過(guò)沒(méi)關(guān)系,我們只需要修改以下DataScrollor的Render Kit,把一些可以定義的值固定下來(lái),再定義一個(gè)TLD文件,就可以在項(xiàng)目中使用簡(jiǎn)化版的Tag了。
這個(gè)方法一開(kāi)始發(fā)布在Myfaces的Wiki中,http://wiki.apache.org/myfaces/WorkingWithLargeTables,那里很少有人關(guān)注到,大家有興趣可以看看原文,本文只是對(duì)這種方法做一些簡(jiǎn)單的介紹,并非自創(chuàng),希望大家能夠多多關(guān)注開(kāi)源社區(qū),因?yàn)槟抢镉凶钚伦詈玫臇|西。
從Nightly Build服務(wù)器中拿到的12.27的Myfaces包,發(fā)現(xiàn)里面擴(kuò)充了很多新的Component,只是并沒(méi)有正式發(fā)布,大家有興趣的話可以研究研究。
posted on 2005-12-30 10:30
steady 閱讀(15018)
評(píng)論(21) 編輯 收藏 所屬分類(lèi):
JSF & Myfaces