前面一篇直接使用了 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.
*/
a
发表评论
评论