您的位置:首页 > Web前端 > JavaScript

在JSF中实现分页(二)

2007-05-09 15:33 435 查看
前面一篇直接使用了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,只是并没有正式发布,大家有兴趣的话可以研究研究。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: