Enterprise Java

Spring & JSF integration: Pagination

When working with large datasets you often need to present data in a paged format. Pagination is an interesting problem because it tends to cut across all layers of your application, from the view tier though application services down to the raw calls to your database.
When it comes to fetching paged data there are some pretty good solutions available. If you are using JPA then you are probably familiar with the setFirstResult() and setMaxResult() methods available on javax.persistence.Query. Even better is the Spring Data JPA project that provides org.springframework.data.domain.Pageable and org.springframework.data.domain.Page interfaces for use directly in your repositories.
With JSF there are also some well documented methods of displaying and fetching paged data. The exact solution will depend on the component suite that you are using but most of them are based around creating a custom javax.faces.model.DataModel implementation. For example MyFaces have suggestions on their wiki, RichFaces have blogged about the problem and PrimeFaces provide a Lazy Loading DataTable.
Recently I have been trying to develop something to ease the burden of the JSF developer and remove the need to create the custom DataModels and the backing beans that expose them. The basic idea is that a JSF component will create a lazy loading DataModel on your behalf using EL expressions to fetch the data as it is need.
Here is an example:
<s:pagedData 
  var="myDataModel" 
  value="#{userRepository.findByLastName(
    backingBean.lastName, pageRequest.offset, pageRequest.pageSize)}"
  pageSize="20" />
This will create a myDataModel variable that will fetch 20 rows of data at a time by calling userRepository.findByLastName(). The EL expression will be called several time as the DataModel is scrolled.
(I am assuming that you are using EL 2.2, if you an older server such as Tomcat 6 you may need to install an updated el-impl.jar.)
Each time the EL expression is called a pageRequest variable is made available. This variable provides access the following context information that may be required when fetching a page of data:
pageNumberThe page number to display
pageSizeThe page size requested
offsetThe offset (first result)
sortColumnThe column used to sort data
sortAscendingIf the sort is in ascending or descending order
filtersA map of filters to apply
One problem with the DataModel created in the above example is that the total number of rows is unknown. To get this information we need to provide an additional expression:
<s:pagedData
  value="#{userRepository.findByLastName(
    backingBean.lastName,pageRequest.offset, pageRequest.pageSize)}"
  rowCount="#{userRepository.countByLastName(backingBean.lastName)}" />
The example above has also dropped the var and pageSize attributes, this will use a default page size of 10 and use a variable name of pagedData.
If you have used Spring Data you may have noticed how similar the pageRequest variable is to the org.springframework.data.domain.Pageable interface. In fact, as long as Spring Data is on your classpath, pageRequest can be cast to Pageable. Furthermore the component understands the org.springframework.data.domain.Page object so you no longer need the rowCount expression.
Here is an example that calls a spring data repository and presents data using MyFaces Tomahawk components. This example also allows you to sort the data by clicking on a column header:
<s:pagedData value="#{userRepository.findByLastName(backingBean.lastName, pageRequest)}" />
<t:dataTable value="#{pagedData}" rows="#{pagedData.pageSize}"
    sortColumn="#{pagedData.sortColumn}" sortAscending="#{pagedData.sortAscending}" var="user">
  <t:column>
    <f:facet name="header">
      <t:commandSortHeader columnName="name">
        <h:outputText value="User Name" />
      </t:commandSortHeader>
    </f:facet>
    <h:outputText value="#{user.name}" />
  </t:column>
  <f:facet name="footer">
    <t:dataScroller paginator="true" paginatorMaxPages="9" />
  </f:facet>
</t:dataTable>
One final trick up our sleeves is to ensure that when using PrimeFaces the created DataModel is compatible with org.primefaces.model.LazyDataModel. Here the same example as above but using PrimeFaces components:
<s:pagedData value="#{userRepository.findByLastName(backingBean.lastName, pageRequest)}" />
<p:dataTable value="#{pagedData}" rows="#{pagedData.pageSize}" 
     paginator="true" lazy="true" var="user">
  <p:column headerText="User Name" sortBy="#{user.name}">
    <h:outputText value="#{user.name}" />
  </p:column>
</p:dataTable>
If you want to take a look at any of the code for this it is available on GitHub (look at the org.springframework.springfaces.page.ui and org.springframework.springfaces.model packages). I also have a basic sample application showing page mark-up. As always this code is a moving target so you might encounter some problems running the demos.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button