In an earlier post I introduced Spring Data JPA, which makes it really easy to create a DAO layer. I didn’t get into too much depth, so this time I want to explore a couple of cool features that the DAOs support: pagination and sorting.
Pagination and sorting are useful when you have long lists that you want the user to be able to navigate. Here for example is a UI for a runbook app I’m building. One of the things it allows the user to do is view deployment logs, which we typically want to see in reverse chronological order. Also, since there are lots of logs, we want to page.
There are different ways to design a pagination system from a user experience perspective. Here I’ve done something pretty typical: I have links for first/previous/next/last, and then I show a bounded set of pages around the current page.
The repository
How does Spring Data JPA help? Here’s my DeploymentRepo interface:
package com.example.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.model.Deployment;
public interface DeploymentRepo extends JpaRepository<Deployment, Long> { }
The JpaRepository interface extends Spring Data’s PagingAndSortingRepository interface, so I get some paging/sorting finders for free.
The service
I have a simple service bean that calls the repo:
package com.example.service.impl;
import javax.inject.Inject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.repo.DeploymentRepo;
import com.example.model.Deployment;
import com.example.service.DeploymentLogService;
@Service
@Transactional
public class DeploymentLogServiceImpl implements DeploymentLogService {
private static final int PAGE_SIZE = 50;
@Inject private DeploymentRepo deploymentRepo;
public Page<Deployment> getDeploymentLog(Integer pageNumber) {
PageRequest request =
new PageRequest(pageNumber - 1, PAGE_SIZE, Sort.Direction.DESC, "startTime");
return deploymentRepo.findAll(pageRequest);
}
}
Spring Data uses 0-indexed pages, but I want my service interface to use 1-indexed pages (they will be user-visible and I want the page numbers to be intuitive), so I make the appropriate adjustment in the request. I specify the page size (50 deployments per page), sort direction, and also one or more property names to act as sort keys. Here I’ve chosen startTime, which is a timestamp for the start of the deployment.
That takes care of the Spring Data JPA part, but just for fun, I’ll show you a simplified version of the controller method and JSP too.
The controller
Here’s the controller method:
@RequestMapping(value = "/pages/{pageNumber}", method = RequestMethod.GET)
public String getRunbookPage(@PathVariable Integer pageNumber, Model model) {
Page<Deployment> page = deploymentService.getDeploymentLog(pageNumber);
int current = page.getNumber() + 1;
int begin = Math.max(1, current - 5);
int end = Math.min(begin + 10, page.getTotalPages());
model.addAttribute("deploymentLog", page);
model.addAttribute("beginIndex", begin);
model.addAttribute("endIndex", end);
model.addAttribute("currentIndex", current);
return "deploymentLog";
}
Note again that I’ve adjusted the page numbers to convert Spring Data’s 0-indexing to my app’s 1-indexing.
I’ve precalculated the begin/end indices because JSTL doesn’t have the min and max functions, and anyway, it’s cleaner to do this sort of thing in the controller.
The JSP
Finally here’s the page navigation in the JSP. It uses the Twitter Bootstrap library for the UI, so that’s where the various CSS elements come from.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:url var="firstUrl" value="/pages/1" />
<c:url var="lastUrl" value="/pages/${deploymentLog.totalPages}" />
<c:url var="prevUrl" value="/pages/${currentIndex - 1}" />
<c:url var="nextUrl" value="/pages/${currentIndex + 1}" />
<div class="pagination">
<ul>
<c:choose>
<c:when test="${currentIndex == 1}">
<li class="disabled"><a href="#"><<</a></li>
<li class="disabled"><a href="#"><</a></li>
</c:when>
<c:otherwise>
<li><a href="${firstUrl}"><<</a></li>
<li><a href="${prevUrl}"><</a></li>
</c:otherwise>
</c:choose>
<c:forEach var="i" begin="${beginIndex}" end="${endIndex}">
<c:url var="pageUrl" value="/pages/${i}" />
<c:choose>
<c:when test="${i == currentIndex}">
<li class="active"><a href="${pageUrl}"><c:out value="${i}" /></a></li>
</c:when>
<c:otherwise>
<li><a href="${pageUrl}"><c:out value="${i}" /></a></li>
</c:otherwise>
</c:choose>
</c:forEach>
<c:choose>
<c:when test="${currentIndex == deploymentLog.totalPages}">
<li class="disabled"><a href="#">></a></li>
<li class="disabled"><a href="#">>></a></li>
</c:when>
<c:otherwise>
<li><a href="${nextUrl}">></a></li>
<li><a href="${lastUrl}">>></a></li>
</c:otherwise>
</c:choose>
</ul>
</div>
Spring Data JPA makes it very nice and simple. And Twitter Bootstrap looks great too.


By Donovan June 1, 2012 - 2:42 am
Great tutorial,
Did you committed this anywhere.
It’s also a great webapp bootstrap
By axet June 14, 2012 - 7:57 am
what if i need to filter results by some filed? sort? use web skins / templaes?
By Willie Wheeler June 14, 2012 - 10:21 pm
Yup, Spring Data JPA supports things like findByFoo(Pageable). You have to do a little extra configuration around JPA dialects and such but it’s supported.
By spring mvc + spring data jpa チュートリアル « tikeda123 August 24, 2012 - 6:22 am
[...] and sorting with Spring Data JPA Posted on May 11, 2012 by Willie [...]
By Lukasz August 24, 2012 - 5:41 pm
How to effectively use pagination when dealing with custom queries? The method described in this tutorial applies only when dealing with a single entity type (in this case Deployment), so findAll() can be used. What if we need to write a custom query (a custom findX() method)? My understanding is that a Pageable instance can be passed as the last argument to a custom method, i.e. findX(arg1, arg2, …, Pageable). That works fine, however there is a missing link between Pageable and Page, which provides necessary information required to implement pagging (i.e. max number of elements, etc.). In this case, how to get a reference to a Page instance? Thanks.
By Lukasz August 24, 2012 - 5:45 pm
I think I can answer my own question :) A custom findX() method can have a return type of Page.
By Lukasz August 24, 2012 - 5:48 pm
My last comment was not formatted as I expected. The method should have a parameterized type.
By WIllie Wheeler February 12, 2013 - 6:49 pm
See http://springinpractice.com/2012/06/30/pageable-custom-queries-with-spring-data-jpa/
By Reda September 28, 2012 - 4:10 am
Thanks for your great tutorial,
I just try to do the same thing, but i can’t find a findAll method that takes a PageRequest as a parameter, Have you an idea of what might be the problem?
By Willie Wheeler October 5, 2012 - 12:14 am
Which repository type are you extending?
By Amine Bousta January 31, 2013 - 7:43 am
Thanks for your so neat tutorial.
Btw, you could even do it simpler in your jsp and controller by using an automated binding between the http request and a Page object in your controller.
Just like they point it out in the chapter 1.5.2 of the documentation:
http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/#web-pagination
I’m using that + Page and Pageable objects in the signature of my repositorie’s methods and it’s amazing how things are simplified in my programs.
Spring Data developers are geniuses…
By Willie Wheeler January 31, 2013 - 11:10 pm
Thanks Amine for the tip. I didn’t know about that but it looks useful indeed.
By Kevin Bowersox May 5, 2013 - 10:42 am
This is an excellent and easy to follow tutorial! I added the JSP code into a tag, making things very easy. Good work!
By Willie Wheeler May 5, 2013 - 11:11 am
That’s a great idea.