In this blog post I would like to highlight and solve problem that I recently faced in my project. Together with my team, we are building web application (using JSF 1.2, RichFaces 3.3 and Seam 2.2) that lists data from database in various forms. So far, nothing extraordinary. One of the ways the application is listing its data is using Extended Data Table (EDT) for data that we want to filter, sort and paginate. The data displayed in EDT have generally these characteristics:


  • there are lots of records (thousands)
  • it is using Hibernate ORM to map between Java and DB worlds
  • the entities are using eager fetching for some relations (not all of properties are necessarily displayed in table)
  • common use case is that user does not need all records but searches desired one using filtering

 

The problems that we naturally faced when EDT was used were:


  • basic EDT model (list of objects) loads all entities in memory and then calculates pagination
  • as it loads all entities (with all relations), memory footprint is significant
  • filtering and sorting is done in memory
  • you load lots of data that you don't need

 

This facts forced me to do some investigation about what customizations EDT provide to make its functioning bit more effective. I found couple of articles that focused on basic Data Table but when I tried to use it, it was not functioning as I desired. So, next are the artifacts that formed my solution that have following characteristics:


  • application loads only the data that are immediately displayed in EDT
  • filtering and sorting is done in database
  • when row is selected in the table, additional information that is necessary (for processing or displaying) is loaded
  • for pagination, count of rows that fulfil filtering criteria is retrieved from DB not using resultList.size()

 

With this requirements I started to dig possible solution for my problem. Armed with good tip from StackOverflow (link in references) I reused the code from there and added my own improvements that further fulfilled my goals. So the solution consists of 4 parts:

 

  • the page that contains EDT
  • action component that handles actions requested on the page
  • data model component that handles operations with EDT
  • data provider component that handles requests for data for EDT

 

In this post, I will demonstrate the problem on a table listing Person object that contains some information about a person. This means, that components used contain this Person keyword in their names.

 

The page with EDT

 

The page itself is very simple and the EDT usage is no different than any other EDT usage. The snippet with the most important parts and little explanation is following.

 

[Complete page source | https://martin-hynar-blog.googlecode.com/svn/trunk/richfaces-extendeddatatable/extendeddatatable/src/main/webapp/index.xhtml]

 

<rich:extendedDataTable
  value="#{EDTAction.personsDataModel}"
  selection="#{EDTAction.personSelection}"
  binding="#{personsTable}"
  ...>

 

As you probably know, value attribute references a model from which the table takes data. In the simple and most straightforward form, this is List of objects. This attribute is the most important from the perspective of the goals that were set above. The selection attribute then references a property to which information about selected row(s) is stored and binding references property that holds state information of the table. I use the properties referenced by selection and binding to manipulate selected data in action bean which is EDTAction in this case.

 

Action bean - EDTAction

 

The action bean is here just to glue all the components together. So, without deeper description, I list important code snippet from EDTAction.

 

[Complete EDTAction source | https://martin-hynar-blog.googlecode.com/svn/trunk/richfaces-extendeddatatable/extendeddatatable/src/main/java/org/jboss/edt/EDTAction.java]

 

@Name("EDTAction")
public class EDTAction {

    @In(required=false)
    @Out(required=false)
    private HtmlExtendedDataTable personsTable;

    private SimpleSelection personSelection;

    @In(value = "PersonDataModel", create = true)
    private PersonDataModel personsDataModel;
    // In simple scenarios, list can be used without affecting page
    // private List<Person> personsDataModel;

    private Person selectedPerson;

    ...
}

 

The properties here are the ones referenced from EDT in the page. The crucial information here is that the model for EDT is not simple List, but PersonDataModel component. See bellow.

 

EDT data model - PersonDataModel

 

The component is one of the two components that are important for fulfilling the requirements stated. This component handles the requests from EDT and holds the data actually displayed in it. How it looks like ...

 

 

[Complete PersonDataModel source | https://martin-hynar-blog.googlecode.com/svn/trunk/richfaces-extendeddatatable/extendeddatatable/src/main/java/org/jboss/edt/PersonDataModel.java]

 

@Name("PersonDataModel")
@Scope(ScopeType.SESSION)
public class PersonDataModel
    extends ExtendedDataModel
    implements Serializable, Modifiable {
    ...
}

 

It extends ExtendedDataModel from RichFaces API that is default model used for EDT. If simple list is used as data container in EDTAction, then RichfFaces will use ExtendedDataModel behind the scenes and all data will be in the list. This is not desired, so here, the default data model is extended and as described bellow, uses own store for data. Also, Modifiable interface is implemented which allows to modify the model based on the filtering and sorting conditions entered in EDT (see later).

 

So, what is inside PersonDataModel ...

 

public class PersonDataModel
    extends ExtendedDataModel
    implements Serializable, Modifiable {

    @In(value = "PersonDataProvider", create = true)
    private DataProvider<Person> dataProvider;

    private Object rowKey;
    private List<Object> wrappedKeys;
    private Map<Object, Person> wrappedData;
    private Integer rowCount;
    private Integer rowIndex;
    private SequenceRange memoRange;

 

First and very important property of the model component is dataProvider. The reason for this is separation of concerns and following functionality division from RichFaces. PersonDataModel is not responsible for retrieving data from data source but keeping data of EDT. Then we have rowKey which denotes record key for which EDT will ask data; wrappedKeys is collection of keys for actually displayed records; wrappedData is mapping between key and actual data; rowCount is total number of rows that are available and drives pagination; rowIndex contains index of currently selected row. The property memoRange caches range that was displayed last time.

 

These properties are used in methods inherited from ExtendedDataTable and some are passed to dataProvider to control what is retrieved from DB. The important methods are:

 

  • walk - computes the range of records to be displayed and calls back EDT with the key of the record to be rendered (visitor pattern used here). Note also usage of loadData method in case when wrappedKeys is null. This method uses dataProvider to load data for actual view.

 

    public void walk(FacesContext context, DataVisitor visitor, Range range, Object argument) throws IOException {

        int rowC = getRowCount();
        int firstRow = ((SequenceRange) range).getFirstRow();
        int numberOfRows = ((SequenceRange) range).getRows();
        if (numberOfRows <= 0) {
            numberOfRows = rowC;
        }

        if (wrappedKeys != null && memoRange != null
                && memoRange.getFirstRow() == firstRow
                && memoRange.getRows() == numberOfRows) {
            Object key;
            for (Iterator<Object> it = wrappedKeys.iterator(); it.hasNext(); visitor.process(context, key, argument)) {

                key = it.next();
                setRowKey(key);
            }

        } else {
            reset();
            wrappedKeys = new ArrayList<Object>();
            int endRow = firstRow + numberOfRows;
            if (endRow > rowC) {
                endRow = rowC;
            }

            Object key;
            for (Iterator<Person> it = loadData(firstRow, endRow).iterator(); it.hasNext(); visitor.process(context, key, argument)) {

                Person item = it.next();
                key = getKey(item);
                wrappedKeys.add(key);
                wrappedData.put(key, item);
            }

        }
        memoRange = (SequenceRange) range;
    }

 

  • getRowCount - provides number of available records for calculating pagination. If the number is known (this means that nothing changed between rerendering of EDT) then it is returned without asking DB (assuming no data were added/removed).

 

    public int getRowCount() {
        if (rowCount == null) {
            rowCount = new Integer(dataProvider.getRowCount());
        } else {
            return rowCount.intValue();
        }

        return rowCount.intValue();
    }

 

  • getObjectByKey - gets actual record from wrappedData or asks dataProvider to load this object from database.

 

    public Person getObjectByKey(Object key) {
        Person t = wrappedData.get(key);
        if (t == null) {
            t = dataProvider.getItemByKey(key);
            wrappedData.put(key, t);
        }
        return t;
    }

 

  • modify - method inherited from Modifiable that is called by EDT when user enters some filter of asks for sorting of data. This method passes the conditions to dataProvider and resets PersonDataModel (which means that all state information is invalidated because filtering or sorting will cause different records to be displayed). The complete method (not listed here) must contain logic for comparing current and new filters whether some change occurred. If the filters are unchanged, data cached in this component could be preserved, otherwise, new data must be retrieved.

 

    public void modify(List<FilterField> filterFields, List<SortField2> sortFields) {
        if (dataProvider instanceof Sortable2) {
            ((Sortable2) dataProvider).setSortFields(sortFields);
        }

        if (dataProvider instanceof Filterable) {
            ((Filterable) dataProvider).setFilterFields(filterFields);
        }

        // Logic that compares current
        // and new filters if there is a change.
        // If the filters are intact, reset is not called
        reset();
    }

 

Feeding data to the model - PersonDataProvider

 

The last piece into the puzzle is the PersonDataProvider component that is responsible for getting relevant records from DB. The class signature is following:

 

[ Complete PersonDataProvider source | https://martin-hynar-blog.googlecode.com/svn/trunk/richfaces-extendeddatatable/extendeddatatable/src/main/java/org/jboss/edt/PersonDataProvider.java]

 

@Name("PersonDataProvider")
@Scope(ScopeType.SESSION)
public class PersonDataProvider
    implements DataProvider<Person>, Sortable2, Filterable {
    ...
}

 

The class implements 3 interfaces from which DataProvider defines basic contract used by PersonDataModel for asking record range, individual records by key, key of the record and count of available records. Interfaces Sortable2 and Filterable are used to pass sorting and filtering constraints.

 

The implementation of PersonDataProvider is quite self-descriptive so I won't spend much space with it. It works with several facts provided from PersonDataModel

 

  • range of records that is going to be displayed in cooperation with pagination - reflected in getItemsByRange(int firstRow, int numberOfRows)
  • filters for particular entity propeties - passed in setFilterFields(List<FilterField> filterFields)
  • sort conditions - passed in setSortFields(List<SortField2> sortFields)

 

As you can see in the code, to employ dynamic query building I used Criteria API. Using this API and the conditions mention, you can end up with generated query like this:

 

select
  top ? this_.id as id7_0_,
  this_.city as city7_0_,
  this_.email as email7_0_,
  this_.first as first7_0_,
  this_.last as last7_0_,
  this_.street as street7_0_,
  this_.zip as zip7_0_ 
from
  Person this_ 
order by
  this_.id asc

 

Few tips

 

  • In data provider, you can control laziness of the relations using Criteria PI and load only necessary properties for EDT rendering.

 

Criteria criteria = ((org.hibernate.Session) entityManager.getDelegate()).createCriteria(Person.class);
criteria.setFetchMode("kids", FetchMode.SELECT);

 

  • If you work with more complex data where building queries with Criteria API becomes complicated (e.g. filtering/sorting on properties from related tables) it is easier to come with permutation of predefined HQL queries. This allows also using constructs that are not possible in Criteria API (e.g. DB based SELECT UNIQUE ...)

 

Takeaway

 

In this post, complete codes were not listed, but you can still get them via links above the listings. If you are interested in trying a simple application and listing some data in EDT customized using this solution, you can checkout anonymously the Mavenized Eclipse project from https://martin-hynar-blog.googlecode.com/svn/trunk/richfaces-extendeddatatable/extendeddatatable

 

I have verified the solution on JBoss AS 6.1.0.Final and you can reach the person listing on http://localhost:8080/edt

 

The configuration of the persistence in the WAR is that SQL commands are printed into server log. You can see there that only limited data are retrieved from DB, not all records. For instant use, example records are auto-inserted. Note also that the application used DefaultDS and Hibernate is configure with create-drop option.

 

References