Thursday, December 27, 2012

Spring Data JPA and pagination

Let us start with the classic JPA way to support pagination.

Consider a simple domain class - A "Member" with attributes first name, last name. To support pagination on a list of members, the JPA way is to support a finder which takes in the offset of the first result(firstResult) and the size of the result(maxResults) to retrieve, this way:

import java.util.List;

import javax.persistence.TypedQuery;

import org.springframework.stereotype.Repository;

import mvcsample.domain.Member;

@Repository
public class JpaMemberDao extends JpaDao<Long, Member> implements MemberDao{

 public JpaMemberDao(){
  super(Member.class);
 }
 @Override
 public List<Member> findAll(int firstResult, int maxResults) {
  TypedQuery<Member> query = this.entityManager.createQuery("select m from Member m", Member.class);
  return query.setFirstResult(firstResult).setMaxResults(maxResults).getResultList();
 }

 @Override
 public Long countMembers() {
  TypedQuery<Long> query = this.entityManager.createQuery("select count(m) from Member m", Long.class);
  return query.getSingleResult();
 }
}

An additional API which returns the count of the records is needed to determine the number of pages for the list of entity, as shown above.

Given this API, two parameters are typically required from the UI:


  • the current page being displayed (say "page.page")
  • the size of list per page (say "page.size")
The controller will be responsible for transforming these inputs to the one required by the JPA - firstResult and maxResults this way:

@RequestMapping(produces="text/html")
public String list(@RequestParam(defaultValue="1", value="page.page", required=false) Integer page, 
   @RequestParam(defaultValue="10", value="page.size", required=false) Integer size, Model model){
 int firstResult = (page==null)?0:(page-1) * size;
 model.addAttribute("members",this.memberDao.findAll(firstResult, size));
 float nrOfPages = (float)this.memberDao.countMembers()/size;
 int maxPages = (int)( ((nrOfPages>(int)nrOfPages) || nrOfPages==0.0)?nrOfPages+1:nrOfPages);
 model.addAttribute("maxPages", maxPages);
 return "members/list";
}

Given a list as a model attribute and the count of all pages(maxPages above), the list can be transformed to a simple table in a jsp, there is a nice tag library that is packaged with Spring Roo which can be used to present the pagination element in a jsp page, I have included it with the reference.



So this is the approach to pagination using JPA and Spring MVC.

Spring-Data-JPA makes this even simpler, first is the repository interface to support retrieving a paginated list - in its simplest form the repository simply requires extending Spring-Data-JPA interfaces and at runtime generates the proxies which implements the real JPA calls:

import mvcsample.domain.Member;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

public interface MemberRepository extends JpaRepository<Member, Long>{
 //
}

Given this, the controller method which accesses the repository interface is also very simple:

@RequestMapping(produces="text/html")
public String list(Pageable pageable, Model model){
 Page<Member> members = this.memberRepository.findAll(pageable);
    model.addAttribute("members", members.getContent());
    float nrOfPages = members.getTotalPages();
    model.addAttribute("maxPages", nrOfPages);
 return "members/list";
}

The controller method accepts a parameter called Pageable, this parameter is populated using a Spring MVC HandlerMethodArgumentResolver that looks for request parameters by name "page.page" and "page.size" and converts them into the Pageable argument. This custom HandlerMethodArgumentResolver is registered with Spring MVC this way:

<mvc:annotation-driven>
 <mvc:argument-resolvers>
  <bean class="org.springframework.data.web.PageableArgumentResolver"></bean>
 </mvc:argument-resolvers>
</mvc:annotation-driven> 

the JpaRepository API takes in the pageable argument and returns a page, internally automatically populating the count of pages also which can retrieved from the Page methods.

If the queries need to be explicitly specified then this can be done in a number of ways, one of which is the following:

@Query(value="select m from Member m", countQuery="select count(m) from Member m")
Page<Member> findMembers(Pageable pageable);


One catch which I could see is that that pageable's page number is 0 indexed, whereas the one passed from the UI is 1 indexed, however the PageableArgumentResolver internally handles and converts the 1 indexed UI page parameter to the required 0 indexed value.

Spring Data JPA thus makes it really simple to implement a paginated list page.

I am including a sample project which ties all this together, along with the pagination tag library which makes it simple to show the paginated list.

Reference:
A sample projects which implements a paginated list is available here : https://github.com/bijukunjummen/spring-mvc-test-sample.git

Spring-Data-JPA reference: http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/




1 comment: