You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by Apache Wiki <wi...@apache.org> on 2010/06/02 05:15:48 UTC
[Myfaces Wiki] Update of "WorkingWithLargeTables" by MarkRicard
Dear Wiki user,
You have subscribed to a wiki page or wiki category on "Myfaces Wiki" for change notification.
The "WorkingWithLargeTables" page has been changed by MarkRicard.
The comment on this change is: PagedListDataModel's fetchPage could be called multiple times in a row for the same startRow when it is not needed and the code in fetchPage could be very expensive. .
http://wiki.apache.org/myfaces/WorkingWithLargeTables?action=diff&rev1=10&rev2=11
--------------------------------------------------
*/
public abstract class PagedListDataModel<T> extends DataModel {
- int pageSize;
- int rowIndex;
+ private int pageSize;
+ private int rowIndex;
- DataPage<T> page;
+ private DataPage<T> page;
-
- /*
+
+ private int lastStartRow = -1;
+ private DataPage<T> lastPage;
+
+ /*
- * Create a datamodel that pages through the data showing the specified
+ * Create a datamodel that pages through the data showing the specified
- * number of rows on each page.
+ * number of rows on each page.
- */
+ */
- public PagedListDataModel(int pageSize) {
+ public PagedListDataModel(int pageSize) {
- super();
+ super();
- this.pageSize = pageSize;
+ this.pageSize = pageSize;
- this.rowIndex = -1;
+ this.rowIndex = -1;
- this.page = null;
+ this.page = null;
- }
+ }
- /**
+ /**
- * Not used in this class; data is fetched via a callback to the
+ * Not used in this class; data is fetched via a callback to the fetchData
- * fetchData method rather than by explicitly assigning a list.
+ * method rather than by explicitly assigning a list.
- */
+ */
- @Override
+ @Override
- public void setWrappedData(Object o) {
+ public void setWrappedData(Object o) {
- throw new UnsupportedOperationException("setWrappedData");
+ throw new UnsupportedOperationException("setWrappedData");
- }
+ }
- @Override
+ @Override
- public int getRowIndex() {
+ public int getRowIndex() {
- return rowIndex;
+ return rowIndex;
- }
+ }
- /**
+ /**
- * Specify what the "current row" within the dataset is. Note that
+ * Specify what the "current row" within the dataset is. Note that the
- * the UIData component will repeatedly call this method followed
+ * UIData component will repeatedly call this method followed by getRowData
- * by getRowData to obtain the objects to render in the table.
+ * to obtain the objects to render in the table.
- */
+ */
- @Override
+ @Override
- public void setRowIndex(int index) {
+ public void setRowIndex(int index) {
- rowIndex = index;
+ rowIndex = index;
- }
+ }
- /**
+ /**
- * Return the total number of rows of data available (not just the
+ * Return the total number of rows of data available (not just the number of
- * number of rows in the current page!).
+ * rows in the current page!).
- */
+ */
- @Override
+ @Override
- public int getRowCount() {
+ public int getRowCount() {
- return getPage().getDatasetSize();
+ return getPage().getDatasetSize();
- }
-
- /**
+ }
+
+ /**
- * Return a DataPage object; if one is not currently available then
+ * Return a DataPage object; if one is not currently available then fetch
- * fetch one. Note that this doesn't ensure that the datapage
+ * one. Note that this doesn't ensure that the datapage returned includes
- * returned includes the current rowIndex row; see getRowData.
+ * the current rowIndex row; see getRowData.
- */
+ */
- private DataPage<T> getPage() {
+ private DataPage<T> getPage() {
- if (page != null)
+ if (page != null)
- return page;
-
+ return page;
+
- int rowIndex = getRowIndex();
+ int rowIndex = getRowIndex();
- int startRow = rowIndex;
+ int startRow = rowIndex;
- if (rowIndex == -1) {
+ if (rowIndex == -1) {
- // even when no row is selected, we still need a page
+ // even when no row is selected, we still need a page
- // object so that we know the amount of data available.
+ // object so that we know the amount of data available.
- startRow = 0;
- }
+ startRow = 0;
+ }
- // invoke method on enclosing class
+ // invoke method on enclosing class
- page = fetchPage(startRow, pageSize);
+ page = suggestFetchPage(startRow, pageSize);
- return page;
- }
+ return page;
+ }
- /**
+ /**
- * Return the object corresponding to the current rowIndex.
+ * Return the object corresponding to the current rowIndex. If the DataPage
- * If the DataPage object currently cached doesn't include that
- * index then fetchPage is called to retrieve the appropriate page.
- */
+ * object currently cached doesn't include that index then fetchPage is
+ * called to retrieve the appropriate page.
+ */
- @Override
+ @Override
- public Object getRowData(){
+ public Object getRowData() {
- if (rowIndex < 0) {
+ if (rowIndex < 0) {
+ throw new IllegalArgumentException("Invalid rowIndex for PagedListDataModel; not within page");
+ }
- throw new IllegalArgumentException(
- "Invalid rowIndex for PagedListDataModel; not within page");
- }
- // ensure page exists; if rowIndex is beyond dataset size, then
+ // ensure page exists; if rowIndex is beyond dataset size, then
- // we should still get back a DataPage object with the dataset size
+ // we should still get back a DataPage object with the dataset size
- // in it...
+ // in it...
- if (page == null) {
+ if (page == null) {
- page = fetchPage(rowIndex, pageSize);
+ page = suggestFetchPage(rowIndex, pageSize);
- }
+ }
- // Check if rowIndex is equal to startRow,
+ // Check if rowIndex is equal to startRow,
- // useful for dynamic sorting on pages
+ // useful for dynamic sorting on pages
+
+ if (rowIndex == page.getStartRow()) {
+ page = suggestFetchPage(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 = suggestFetchPage(rowIndex, pageSize);
+ startRow = page.getStartRow();
+ }
+ else if (rowIndex >= endRow) {
+ page = suggestFetchPage(rowIndex, pageSize);
+ startRow = page.getStartRow();
+ }
+
+ return page.getData().get(rowIndex - startRow);
+ }
+
+ @Override
+ 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.
+ */
+ @Override
+ public boolean isRowAvailable() {
+ DataPage<T> 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;
+ }
+ }
+
+ /**
+ * the jsf framework can be funky. It could ask for the same start row multiple times within the
+ * same cycle. Therefore, we cache the results for the startRow and make sure that if the framework
+ * asks for them again in very short order that we simply returned the cached value. What the
+ * extended class could do in fetchPage could be costly, so we make sure it is called only when needed!
+ *
+ * @param startRow
+ * @param pageSize
+ * @return
+ */
+ public DataPage<T> suggestFetchPage(int startRow, int pageSize) {
+ if(this.lastStartRow == startRow) {
+ return this.lastPage;
+ }
- if (rowIndex == page.getStartRow()){
+ this.lastStartRow = startRow;
- page = fetchPage(rowIndex, pageSize);
+ this.lastPage = fetchPage(startRow, pageSize);
- }
+ return this.lastPage;
+ }
+ /**
- 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);
- }
-
- @Override
- 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.
- */
- @Override
- public boolean isRowAvailable() {
- DataPage<T> 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
+ * Method which must be implemented in cooperation with the managed bean
- * managed bean class to fetch data on demand.
+ * class to fetch data on demand.
- */
+ */
- public abstract DataPage<T> fetchPage(int startRow, int pageSize);
+ public abstract DataPage<T> fetchPage(int startRow, int pageSize);
}
- }}}
Finally, the managed bean needs to provide a simple inner class that provides the fetchPage implementation: