Spring Boot pagination
last modified July 6, 2020
Spring Boot pagination tutorial shows how to paginate data in a Spring application.
Spring is a popular Java application framework and Spring Boot is an evolution of Spring that helps create stand-alone, production-grade Spring based applications easily.
Pagination
Pagination is the process of dividing data into suitable chunks to save resources.
PagingAndSortingRepository
PagingAndSortingRepository
is an extension of CrudRepository
to provide additional methods to retrieve entities using pagination and sorting.
Spring Boot paginate example
In the following application, we create a simple Spring Boot Restful application which allows to paginate data.
pom.xml src ├───main │ ├───java │ │ └───com │ │ └───zetcode │ │ │ Application.java │ │ ├───controller │ │ │ MyController.java │ │ ├───model │ │ │ Country.java │ │ ├───repository │ │ │ CountryRepository.java │ │ └───service │ │ CountryService.java │ │ ICountryService.java │ └───resources │ application.properties │ import.sql └───test └───java
This is the project structure.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zetcode</groupId> <artifactId>repositoryex</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> </parent> <dependencies> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
This is the Maven build file. The h2
dependency includes the H2 database
driver.
Spring Boot starters are a set of convenient dependency descriptors which
greatly simplify Maven configuration. The spring-boot-starter-parent
has some common configurations for a Spring Boot application.
The spring-boot-starter-web
enables web applications, both classic
and RESTFul. The spring-boot-starter-web-freemarker
is a starter
for building web applications with Freemarker template engine. It uses Tomcat as
the default embedded container. The spring-boot-starter-data-jpa
is a starter
for using Spring Data JPA with Hibernate.
The spring-boot-maven-plugin
provides Spring Boot support in Maven, allowing us
to package executable JAR or WAR archives. Its spring-boot:run
goal runs the
Spring Boot application.
spring.main.banner-mode=off spring.jpa.database=h2 spring.jpa.hibernate.dialect=org.hibernate.dialect.H2Dialect spring.jpa.hibernate.ddl-auto=create-drop
In the application.properties
file we write various configuration settings
of a Spring Boot application. With the banner-mode
property we turn
off the Spring banner.
The JPA database
value specifies the target database to operate on.
We specify the Hibernate dialect, org.hibernate.dialect.H2Dialect
in our case.
The ddl-auto
is the data definition language mode; the create-drop
option automatically creates and drops the database schema. The H2 database is run in memory.
INSERT INTO countries(name, population) VALUES('China', 1382050000); INSERT INTO countries(name, population) VALUES('India', 1313210000); INSERT INTO countries(name, population) VALUES('USA', 324666000); INSERT INTO countries(name, population) VALUES('Indonesia', 260581000); INSERT INTO countries(name, population) VALUES('Brazil', 207221000); INSERT INTO countries(name, population) VALUES('Pakistan', 196626000); INSERT INTO countries(name, population) VALUES('Nigeria', 186988000); INSERT INTO countries(name, population) VALUES('Bangladesh', 162099000); INSERT INTO countries(name, population) VALUES('Russia', 146838000); INSERT INTO countries(name, population) VALUES('Japan', 126830000); INSERT INTO countries(name, population) VALUES('Mexico', 122273000); INSERT INTO countries(name, population) VALUES('Philippines', 103738000); INSERT INTO countries(name, population) VALUES('Ethiopia', 101853000); INSERT INTO countries(name, population) VALUES('Vietnam', 92700000); INSERT INTO countries(name, population) VALUES('Egypt', 92641000); INSERT INTO countries(name, population) VALUES('Germany', 82800000); INSERT INTO countries(name, population) VALUES('the Congo', 82243000); INSERT INTO countries(name, population) VALUES('Iran', 82800000); INSERT INTO countries(name, population) VALUES('Turkey', 79814000); INSERT INTO countries(name, population) VALUES('Thailand', 68147000); INSERT INTO countries(name, population) VALUES('France', 66984000); INSERT INTO countries(name, population) VALUES('United Kingdom', 60589000); INSERT INTO countries(name, population) VALUES('South Africa', 55908000); INSERT INTO countries(name, population) VALUES('Myanmar', 51446000); INSERT INTO countries(name, population) VALUES('South Korea', 68147000); INSERT INTO countries(name, population) VALUES('Colombia', 49129000); INSERT INTO countries(name, population) VALUES('Kenya', 47251000); INSERT INTO countries(name, population) VALUES('Spain', 46812000); INSERT INTO countries(name, population) VALUES('Argentina', 43850000); INSERT INTO countries(name, population) VALUES('Ukraine', 42603000); INSERT INTO countries(name, population) VALUES('Sudan', 41176000); INSERT INTO countries(name, population) VALUES('Algeria', 40400000); INSERT INTO countries(name, population) VALUES('Poland', 38439000); INSERT INTO countries(name, population) VALUES('Canada', 37742154); INSERT INTO countries(name, population) VALUES('Morocco', 36910560); INSERT INTO countries(name, population) VALUES('Saudi Arabia', 34813871); INSERT INTO countries(name, population) VALUES('Uzbekistan', 33469203); INSERT INTO countries(name, population) VALUES('Peru', 32971854); INSERT INTO countries(name, population) VALUES('Angola', 32866272); INSERT INTO countries(name, population) VALUES('Malaysia', 32365999); INSERT INTO countries(name, population) VALUES('Mozambique', 31255435); INSERT INTO countries(name, population) VALUES('Ghana', 31072940); INSERT INTO countries(name, population) VALUES('Yemen', 29825964); INSERT INTO countries(name, population) VALUES('Nepal', 29136808); INSERT INTO countries(name, population) VALUES('Venezuela', 28435940);
The schema is automatically created by Hibernate; later, the import.sql
file is executed to fill the table with data.
package com.zetcode.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import java.util.Objects; @Entity @Table(name = "countries") public class Country { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private int population; public Country() { } public Country(Long id, String name, int population) { this.id = id; this.name = name; this.population = population; } public Long getId() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPopulation() { return population; } public void setPopulation(int population) { this.population = population; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Country country = (Country) o; return population == country.population && Objects.equals(id, country.id) && Objects.equals(name, country.name); } @Override public int hashCode() { return Objects.hash(id, name, population); } @Override public String toString() { final StringBuilder sb = new StringBuilder("Country{"); sb.append("id=").append(id); sb.append(", name='").append(name).append('\''); sb.append(", population=").append(population); sb.append('}'); return sb.toString(); } }
This is the Country
entity. Each entity must have at least two
annotations defined: @Entity
and @Id
. Previously, we
have set the ddl-auto
option to create-drop
which means
that Hibernate will create the table schema from this entity.
@Entity @Table(name = "countries") public class Country {
The @Entity
annotation specifies that the class is an
entity and is mapped to a database table. The @Table
annotation
specifies the name of the database table to be used for mapping.
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
The @Id
annotation specifies the primary key of an entity and
the @GeneratedValue
gives the generation strategy for the values
of primary keys.
package com.zetcode.repository; import com.zetcode.model.Country; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.stereotype.Repository; @Repository public interface CountryRepository extends PagingAndSortingRepository<Country, Long> { }
CountryRepository
is decorated with the @Repository
annotation. By extending from the Spring PagingAndSortingRepository
,
we have some methods to paginate data.
package com.zetcode.service; import com.zetcode.model.Country; import java.util.List; public interface ICountryService { List<Country> findPaginated(int pageNo, int pageSize); }
ICountryService
contains the findPaginated()
contract method. It contains two parameters: the page number and the
page size.
package com.zetcode.service; import com.zetcode.model.Country; import com.zetcode.repository.CountryRepository; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @Service public class CountryService implements ICountryService { @Autowired private CountryRepository repository; @Override public List<Country> findPaginated(int pageNo, int pageSize) { Pageable paging = PageRequest.of(pageNo, pageSize); Page<Country> pagedResult = repository.findAll(paging); return pagedResult.toList(); } }
CountryService
contains the implementation of the findPaginated()
method.
@Autowired private CountryRepository repository;
CountryRepository
is injected with the @Autowired
annotation.
Pageable paging = PageRequest.of(pageNo, pageSize); Page<Country> pagedResult = repository.findAll(paging);
A PageRequest
is created from the supplied values
and passed to the findAll()
repository method.
package com.zetcode.controller; import com.zetcode.model.Country; import com.zetcode.service.ICountryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class MyController { @Autowired private ICountryService countryService; @GetMapping("/countries/{pageNo}/{pageSize}") public List<Country> getPaginatedCountries(@PathVariable int pageNo, @PathVariable int pageSize) { return countryService.findPaginated(pageNo, pageSize); } }
MyController
handles a request from the client.
@Autowired private ICountryService countryService;
ICountryService
is injected into the countryService
field.
@GetMapping("/countries/{pageNo}/{pageSize}") public List<Country> getPaginatedCountries(@PathVariable int pageNo, @PathVariable int pageSize) { return countryService.findPaginated(pageNo, pageSize); }
We provide the page number and page size as path variables. The values are
passed to the findPaginated()
service method.
package com.zetcode; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
Application
is the entry point which sets up Spring Boot
application.
$ mvn -q spring-boot:run
We run the application.
$ curl localhost:8080/countries/0/5 [{"id":1,"name":"China","population":1382050000},{"id":2,"name":"India","population":1313210000}, {"id":3,"name":"USA","population":324666000},{"id":4,"name":"Indonesia","population":260581000}, {"id":5,"name":"Brazil","population":207221000}]
We get the first page of 5 rows. The indexing starts from 0.
$ curl localhost:8080/countries/1/5 [{"id":6,"name":"Pakistan","population":196626000},{"id":7,"name":"Nigeria","population":186988000}, {"id":8,"name":"Bangladesh","population":162099000},{"id":9,"name":"Russia","population":146838000}, {"id":10,"name":"Japan","population":126830000}]
We get the next page.
In this tutorial, we have shown how to create pagination in a Spring Boot application.