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: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.Reference: Integrating Spring & JavaServer Faces : Pagination from our JCG partner Phillip Webb at the Phil Webb’s Blog blog.