Phil Webb's Blog

Random thoughts from a software developer

Integrating Spring & JavaServer Faces : Pagination

with 2 comments

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:

pageNumber The page number to display
pageSize The page size requested
offset The offset (first result)
sortColumn The column used to sort data
sortAscending If the sort is in ascending or descending order
filters A 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.

About these ads

Written by Phillip Webb

August 7, 2011 at 3:28 pm

2 Responses

Subscribe to comments with RSS.

  1. Great work! This is exactly what I’m looking for.

    BTW. I raise my first issue for springfaces in GitHub.

    https://github.com/philwebb/springfaces/issues/40

    Hope it will be helpful. Cheers.

    Ted Liang (@Ted__Liang)

    October 14, 2011 at 12:16 am

  2. Thanks very much, I am glad you find it useful and thanks for raising the issue, I’ll take a look.

    Phillip Webb

    October 14, 2011 at 6:48 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: