When using a datatable, the general approach is to fetch all the data, bind it to the datatable and let the component do it’s job. This is generally ok when you are working with small sized datasets but what if there are hundereds, thousands or even more amount of data waiting to be displayed. This is likely to lead memory problems and not a good practice. The worse thing happened happened some time ago in our project; sometimes hibernate proxies throw lazy initialization exceptions when a paging occurs. The good news is that after working some time on the problem, I’ve figured out a solution with javax.faces.DataModel.
Actually a component extending from uidata like DataTable interacts with it’s data through an adapter class; DataModel. This class wraps the actual collection for example, if a list is bound to a datatable then a ListDataModel is created and the component use this ListDataModel to access it’s data. DataModel provides methods like getWrappedData, isRowAvailable, getRowData, getRowCount letting the component access the wrapped data the way datamodel determines. To give an example, In case the wrapped data is a list, getRowCount returns list.size().
The solution to the large data problem is to use a custom data model and show only the data page allowed by the page size of the datatable. Whenever a paging occurs, the data of the next page is fetched and displayed, by this way the whole data is never read, the idea is to load only the page size of data when needed. There are two key attributes of a datatable, “first” and “rows”, first refers to the starting element and rows refers to the page size to be displayed. So when the first is 10 and rows is 5, then data between 10 and 15 is displayed. A pager simply sets the first attribute and the rows elements of the datatable, and then datatable displays it’s data considering these attributes. Also when the pages defines the number of pages, it looks at the datatable’s getRowCount method. Following is the PagedDataModel class providing the solution based on these informations.
package forca.barca;
import java.util.List; import javax.faces.model.DataModel;
public class PagedListDataModel extends DataModel{
private int rowIndex = -1; private int totalNumRows; private int pageSize; private List list; public PagedListDataModel() { super(); } public PagedListDataModel(List list, int totalNumRows, int pageSize) { super(); setWrappedData(list); this.totalNumRows = totalNumRows; this.pageSize = pageSize; } public boolean isRowAvailable() { if(list == null) return false; int rowIndex = getRowIndex(); if(rowIndex >=0 && rowIndex < list.size()) return true; else return false; }
public int getRowCount() { return totalNumRows; }
public Object getRowData() { if(list == null) return null; else if(!isRowAvailable()) throw new IllegalArgumentException(); else { int dataIndex = getRowIndex(); return list.get(dataIndex); } }
public int getRowIndex() { return (rowIndex % pageSize); }
public void setRowIndex(int rowIndex) { this.rowIndex = rowIndex; }
public Object getWrappedData() { return list; } public void setWrappedData(Object list) { this.list = (List) list; }
} |
|
The key point is to fool the pager by returning the total list size as the row count. Pager components use this value when rendering themselves, for example a simple pager divides this number to the page size and use the result to render the page numbers. Other important thing is always return the mod(rowIndex) as the rowIndex. Let’s say we have a datatable with page sizes:10. The rendering algorithm of a datatable initially gets the first element and sets it as the rowIndex and then renders the data until the rowIndex <= pagesize. When the user wants to see the next page, since pager sets the first attribute of the datatable as 10, the rowIndex is initially set to 10. Problem occurs here, since we use custom paging and load only 10 blocks of data (0-9), the 10th element is null and isRowAvailable returns false. In order to hack it, whenever the rowIndex is needed, the (rowIndex mod(page)) is returned. This means when the 10th element is needed 10 mod(10) = 0 is returned. Similarly referring to 15th element returns 5th element in the list.
In order to the custom paging at the view layer, we need features to do the same thing at business layer. I’m going to present a way with Hibernate Criteria API, also Query API should do the job. If you are not using spring and hibernate there are other options to fetch paged data like oracle’s rownum. Anyway the method below is located at an Hibernate DAO class and accessed via Spring beans. Also there is another one to return only the size of the actual data.
public List getPagedData(SomeCriteriaObject someObject, int start, int page) { try { Criteria criteria = getSession().createCriteria(ClassToBeQueried.class); //Build Criteria object here criteria.setFirstResult(start); criteria.setMaxResults(page); return criteria.list(); } catch (HibernateException hibernateException) { //do something here with the exception } } |
public int getDataCount(SomeCriteriaObject someObject) { Criteria criteria = getSession().createCriteria(ClassToBeQueried.class); criteria.setProjection(Projections.rowCount());
// Build Criteria object here Number nuofRecords = ((Number) criteria.uniqueResult()); return nuofRecords == null ? 0 : nuofRecords.intValue(); } |
|
Since all the stuff is ready to do custom paging, how to enable it? The first thing is to bind the custom data model to the datatable component as the value.
<h:datatable id=”table1″ value=”#{didYouSeeZidanesHeaderToMaterazzi.myPagedDataModel}“>
…
…
…
</h:datatable>
Finally the last job is to create a PagedDataModel and return it in the getter as;
public DataModel getMyPagedDataModel() { int totalListSize = getSomeBusinessService().getDataCount(getSomeCriteriaObject()); List pagedList = getSomeBusinessService().getPagedData(getSomeCriteriaObject(), getTable1().getFirst(), getTable1().getRows()); PagedDataModel dataModel = new PagedDataModel(pagedList, tatalListSize, getTable1().getRows()); return dataModel; } |
|
The SomeBusinessService is not important and just a business bean providing access to the hibernate daos, also the somecriteriaobject is a simple bean whose members are bound to the page components used when building the criteria object. As I mentioned these are the stuff I’ve used to do custom paging at business level, you can replace it with your own stuff. The important idea is to use a custom data model to enable custom paging at view layer.