JSF Datatable with Custom Paging for Large Datasets

Update: See PrimeFaces DataTable for the built-in lazy loading feature.

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.

About these ads

25 Responses to JSF Datatable with Custom Paging for Large Datasets

  1. Mario Ivankovits says:

    Ok, just in getDataCount you can avoid creating the whole list just to get the number of records.

    Use the projection feature of the Criteria API as follows:
    crit.setProjection(Projections.rowCount());

    Number nuofRecords = ((Number) crit.uniqueResult());
    if (nuofRecords == null)
    {
    return 0;
    }

    return nuofRecords.longValue();

  2. Very nice post (y)

  3. Cagatay says:

    Thanks Mario, actually I was looking for an api to get only the row number instead of list.size(). Your suggestion does it well, thanks for the tip, I’ve updated the entry. See you on the list.

  4. amitev says:

    With this example the hibernate query will be executed when the page is rendered and when the page is submited. That could take 2 queries per request ( not a good idea realy).

  5. Cagatay says:

    Although the second query execution will already be cached in session, it can be easily be refactored to only one hibernate call. The important part is the datamodel solution not how the getter is implemented.

  6. amitev says:

    Thx. Very useful example!

  7. Rizvi says:

    Could you please tell me in the code
    List pagedList = getSomeBusinessService().getPagedData(getSomeCriteriaObject(), getTable1().getFirst(), getTable1().getRows());
    PagedDataModel dataModel = new PagedDataModel(pagedList, tatalListSize, getTable1().getRows());

    How do u access getTable1() or what is the implementation of getTable1()

  8. Cagatay says:

    The best way is to use the “binding” attribute, and bind the component to a variable in the managed bean. The other alternative is to use the findComponentInRoot method.

  9. Jan says:

    Hi, your solution works fine, thank you.
    I have one issue: getDataModel() in the backing bean is called three(!) times per one click on a pagination element.
    So the business methods are called three times what leads to a performance lack.
    This is probably not normal behaviour, is it?
    Jan

  10. Cagatay says:

    Yes, there is room for performance tuning. In my last project, I’ve made the managed beans aware of the jsf lifecycle and added methods like onPageLoad, onPreRender and etc.(Similar to shale’s idea). Anyway onPreRender, I set the value of the datamodel, and the getter just returns it. I guess there are other performance tuning alternatives.

  11. istarliu says:

    Can you give us a hint or a example? thanks!

  12. Vofeka says:

    Hi,
    First of all: Thanks for sharing! the article is very interesting.
    However, could you please give us a (simple) working of your solution?
    Thanks in advance

  13. robert says:

    hi, there:
    thank you for your solution.

    moreover, i wanna ask you how did you implement onPreRender method, since i’m trying this also. but i found that the getRowIndex() always returns 0. so i cannot jump into the corresponding data records – always display the top records.
    any suggestions? thanks in advance…

  14. John says:

    This solution is great, and working well. But in order to tune performance, I have a solution. Add a new boolean field in DataModel named ‘populated’, when u first call getDataModel(), u can check the ‘populated’ is true or false. As u can see, it is false, so call the hibernate to fetch the PagedDataModel, and then set the ‘populated’ to true. So when u secondly or thirdly enter the getDataModel, u can just return the last polulated DataModel. Have a try, any problem let me know.

  15. Rocky says:

    > new PagedDataModel(pagedList, tatalListSize,

    Yup I like big tatas too.

  16. Marcio says:

    Anyone knows how to implement the lifecycle way to populated the datamodel proposed by Cagatay?

  17. mojjy says:

    I encountered a small problem using this, (probably due to my lack of knowledge of JSF)

    I have a search criteria/search filter for the user to narrow down results. Lets say I search and there is 4 pages of results, and I go to the 4th page. Now I do another search, and there is only one page of results, I get the IllegalArgumentException(“Invalid rowIndex”).

    I sort of fixed this problem by moving the


    if (rowIndex == page.getStartRow()){
    page = fetchPage(rowIndex, pageSize);
    }

    from the getRowData() method into the isRowAvailable() method.

    Also if my search new search criteria results in the total number of records changing I do the following,

    javax.faces.component.UIData requests = (UIData)findComponent(“tableId”);
    requests.setFirst(0);

    This fixed the problem, but don’t take my word as my knowledge of JSF is scary.

  18. alex_ro_bv says:

    Hi, your method is interesting, I’ve tried it but it does not do anything, probably because of my lack of information on jsf.
    My list however is populated , I think the custom datamodel is also well populated but it just won’t show my infos on the table. I use

    where offer is the PagedListDataModel populated in the constructor of OfferHelper. Any idea how to use the dataTable in this case? Any example you can provide?

  19. pau says:

    HI cagatay,
    kinda a newbie in jsf, but I just want to know if the is needed in order to implement the paging mechanism? I jsut cant imagine how the user can do the paging without the scroller.

  20. cagataycivici says:

    Pau, this is just a performance hack, paging in JSF is very easy actually, just try tomahawk’s scroller component for example.

  21. pau says:

    cagatay,
    thanks for answering. Yeah you’re right paging using the scroller is quite easy. I was able to do the on demand loading for the table as well in combination with the rich scroller. The problem is the call to getPagedData(), it is always called twice. Once during the Apply Request Values and another one during the Render Response Phase. The call to the Apply Request Values contains the previous row value and the call to the Render Response contains the current row value which is the right value. Because of this behavior, a query to the database gets called twice, one for the old and another one for the new value of the row (when i say row i pertain to the page that the scroller produces). Any leads as to why is this happening at all?

  22. Hoju says:

    First i have to say that very good and usefull example!

    My code does a twice getMyPagedDataModel -method, i’ve read these site’s and here was a solutions for the problem. I just wondering how onPageLoad on the jsf lifecycle work?

  23. Madhav says:

    Hi Cagatay,

    We took the idea from the solution you have provided above. Since we are using Oracle XQL we could not use Hibernate directly for pagination and had to develop our own method from at the DAO layer.

    There is one problem that you have not addressed in this blog – “Sorting”. Whenever the user clicks on a column header in the data table then the entire result needs to be sorted based on that column and then that page’s result needs to be displayed.

    The problem that we are facing is with the rendering. With preserveDataModel = “true” it gives an error of duplicate id’s in the JSP and with it set to “false” it first makes a call to DB to get the DataModel and then on the second click sorts which is totally un-usable.

    Can you provide the entire code for the example above, especially the JSP?

    Thanks,
    Madhav

  24. Pingback: Lazy loading of tables in JSF - working with large amount of data « Eivind’s Weblog

Follow

Get every new post delivered to your Inbox.

Join 106 other followers

%d bloggers like this: