Custom Pagination Tags With Spring Data JPA

Thu, 05/25/2017 - 14:55

The pagination allows us to split the data content list into evenly distributed chunks and represent them as if separate pages, hence the naming. It also gives a user an opportunity to navigate easily through the pages. The best example I can come up with is the Goooooooooogle pagination, when you search something and the search result comes with a previous link, an increasing sequence of 10 consecutive integer links and a next link. Here we are going to create a similar one for a random entity data type using Spring Data JPA. We will  create a custom reusable tag within /WEB-INF/tags/almighty.tag file of our project and prefix it with ara so that we can call it as <ara:almighty />. It sounds cool, doesn't it?

To use Spring Data JPA let's just use Maven and add the dependency in our pom.xml. If you are using Spring Boot the version is managed automatically and all you need to add inside pom.xml is the following

</dependencies>	
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
</dependencies>

Let's imagine we have an @Entity Status class that contains a private Long id as a primary key of non-primitive type, a private String text parameter for storing our status and a parameter of type java.util.Date showing when the status is created.

@Temporal(TemporalType.TIMESTAMP)
private Date createdOn;

In order to carry out the persistent related actions to and from the database the Spring Data JPA provides support for creating JPA repositories by extending the Spring Data repository interfaces. This extra layer of abstraction comes with a pleasing tradeoff. We do not need to make our own implementation of the extended interface in order to use Spring Data JPA. The interface we are going to extend here is the PagingAndSortingRepository<Status, Long>  that declares the methods that are used to sort and paginate status entities retrieved from the database. So let's create a StatusDao interface and extend it.

import org.springframework.data.repository.PagingAndSortingRepository;

public interface StatusDao extends PagingAndSortingRepository<Status, Long> {

}

The magical thing is that we do not have to do anything more with this in order to actually use it, because incredibly Spring Data JPA is going to supply an implementation for this interface that we just created above.

Let's inject the StatusDao bean object into the Service layer. For that reason we just create a new StatusService class.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import packagename.Status;
import packagename.StatusDao;

@Service
public class StatusService {

  public final static int PAGESIZE = 3;

  @Autowired
  private StatusDao statusDao;

  public Page<Status> getPage(int pageNumber) {
    PageRequest request = 
    new PageRequest(pageNumber-1, PAGESIZE, Sort.Direction.DESC, "createdOn");
    return statusDao.findAll(request);
  }

}

Our pages are going to contain not more than 3 statuses. We can get the status list of each page by the getPage method. As you can see we call a findAll method on statusDao autowired object without even implementing the created StatusDao interface. Abracadabra ta-da. The statuses are going to be retrieved in descending chronological order of the created date. We wrote pageNumber-1 as an argument because we do not want 0 page number based numeration for our getPage method. So our numeration will start from 1. We will be able to pass the page numbers through a p request parameter /statuses?p=1.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import packagename.Status;
import packagename.StatusService;

@Controller
public class PaginationController {

  @Autowired
  private StatusService statusService;
	
  @RequestMapping(value="/statuses", method=RequestMethod.GET)
  String statuses(Model model, @RequestParam(name="p", defaultValue="1") int pageNumber) {
    Page<Status> page = statusService.getPage(pageNumber);		
    model.addAttribute("page", page);
    return "app.statuses";
  }
}

The statuses method of our controller will be mapped to the desired path. We get the Page<Status> page object and pass it to the rendered UI view. The returned string "app.statuses" could be the <definition> tag name inside xml configuration file of the Apache Tiles, but we are free to use any templating framework we want.

Next, we are going to create a custom pagination tag using JSTL standard tag library and include it in our java server page statuses.jsp. The object of the Page class is going to be available in our statuses.jsp, because we passed it through the controller class method, so we can access it using expression language. We will pass it to our custom tag as a page attribute. Together with page we will have two more attributes url and size to pass accordingly the shared part of the url for pagination links and the size of the consecutively numbered page links. The size attribute by default is going to have a value of 10 pages if not specified in our custom tag. In the end of the day the statuses.jsp will look very simple because we will be able to inject the custom pagination tag  in the available DOM structure wherever we want.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix = "ara" tagdir = "/WEB-INF/tags" %>

<c:url var="url" value="/statuses"></c:url>

<%-- some tags --%>

<ara:almighty page="${page}" url="${url}" size="5"/>

<%-- some more tags --%>

Time to roll up your sleeves! Go ahead and create the /WEB-INF/tags/almighty.tag file. We can retrieve all the necessary variables from page instance calling on it a couple of useful methods. For example we can get the number of total pages calling getTotalPages() method, which in the expression language would look like ${page.totalPages}. To get the current  0-based page number we use getNumber() method or ${page.number + 1} in EL to get the current 1-based page number.

<%@ tag language="java" pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix = "fmt" uri = "http://java.sun.com/jsp/jstl/fmt" %>

<%-- Declaration of page, url and size tag attributes --%>

<%-- The page attribute of type org.springframework.data.domain.Page --%>
<%@ attribute name="page" required="true" type="org.springframework.data.domain.Page"%>

<%-- The url path before the page request parameter  (url?p=1) --%>
<%@ attribute name="url" required="true"%>

<%--Number of page numbers to display at once --%>
<%@ attribute name="size" required="false"%>

<%-- Declaration of the default size value --%>
<c:set var="size" value="${empty size ? 10 : size}"/>

<%-- half_size_floor = floor(size/2)  is used to display the current page in the middle --%>
<c:set var="N" value="${size/2}"/>
<c:set var="half_size_floor">
  <fmt:formatNumber value="${N-(1-(N%1))%1}" type="number" pattern="#"/>
</c:set>

<%-- current variable stands for the current page number  --%>
<c:set var="current" value="${page.number+1}"/>

<c:set var="startPage" value="${current < half_size_floor + 1 ? 1 : current - half_size_floor }"/>
<c:set var="startPage" value="${current > page.totalPages - half_size_floor ? page.totalPages - size + 1 : startPage }"/>
<c:set var="endPage" value="${startPage+size-1}"/>
<c:set var="endPage" value="${endPage > page.totalPages ? page.totalPages : endPage}"/>

<%--less pages then the size of the block --%>
<c:set var="startPage" value="${page.totalPages < size ? 1 : startPage}"/>
<c:set var="endPage" value="${page.totalPages < size ? page.totalPages : endPage}"/>

<%-- PAGE NAVIGATION LINKS --%>
<div>

  <%-- Previous link --%>
  <c:if test="${current > half_size_floor + 1 and page.totalPages > size}">
    <a href="${url}?p=${current-1}">&lt;prev</a>
  </c:if>

  <%-- Numerated page links --%>
  <c:forEach var="pageNumber" begin="${startPage}" end="${endPage}">
    <c:choose>
      <c:when test="${current != pageNumber}">
        <a href="${url}?p=${pageNumber}"><c:out value="${pageNumber}"/></a>
      </c:when>
      <c:otherwise>
        <a class="current" href="${url}?p=${pageNumber}"><c:out value="${pageNumber}"/></a>
      </c:otherwise>
    </c:choose>             
    <c:if test="${pageNumber != endPage}">|</c:if>
  </c:forEach>

  <%-- Next link --%>
  <c:if test="${current < page.totalPages - half_size_floor + (1-size%2) and page.totalPages > size}">
    <a href="${url}?p=${current+1}">next&gt;</a>
  </c:if>
</div>