Comprehensive Guide to Pagination and Sorting in Spring Boot with Spring Data JPA

Pagination and sorting are essential features for developing scalable and performant applications that deal with large datasets. Spring Data JPA provides built-in mechanisms to easily implement these functionalities.


1. Setting Up a Spring Boot Project

Dependencies

To get started, include the necessary dependencies in your pom.xml if using Maven:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

2. Database Configuration

Set up the database configuration in application.properties:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true

3. Defining the Entity

Create a sample entity for demonstration purposes:

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String category;
    private Double price;

    // Getters and Setters
}

4. Creating the Repository

Spring Data JPA provides the PagingAndSortingRepository and JpaRepository interfaces, which support pagination and sorting.

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
    Page<Product> findByCategory(String category, Pageable pageable);
}

5. Pagination and Sorting in the Service Layer

Implement pagination and sorting in the service layer:

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.data.domain.Sort;
import org.springframework.stereotype.Service;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    public Page<Product> getAllProducts(int page, int size, String sortBy, String sortDir) {
        Sort sort = sortDir.equalsIgnoreCase(Sort.Direction.ASC.name()) ?
                Sort.by(sortBy).ascending() : Sort.by(sortBy).descending();
        Pageable pageable = PageRequest.of(page, size, sort);
        return productRepository.findAll(pageable);
    }

    public Page<Product> getProductsByCategory(String category, int page, int size) {
        Pageable pageable = PageRequest.of(page, size);
        return productRepository.findByCategory(category, pageable);
    }
}

6. Exposing Pagination and Sorting via Controller

Create a REST controller to handle API requests:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/products")
    public Page<Product> getAllProducts(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(defaultValue = "id") String sortBy,
            @RequestParam(defaultValue = "asc") String sortDir) {
        return productService.getAllProducts(page, size, sortBy, sortDir);
    }

    @GetMapping("/products/category")
    public Page<Product> getProductsByCategory(
            @RequestParam String category,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        return productService.getProductsByCategory(category, page, size);
    }
}

7. Customizing Pagination Responses

To customize the pagination response, wrap the Page object in a custom DTO:

import java.util.List;

public class PaginatedResponse<T> {

    private List<T> content;
    private int pageNumber;
    private int pageSize;
    private long totalElements;
    private int totalPages;
    private boolean last;

    public PaginatedResponse(Page<T> page) {
        this.content = page.getContent();
        this.pageNumber = page.getNumber();
        this.pageSize = page.getSize();
        this.totalElements = page.getTotalElements();
        this.totalPages = page.getTotalPages();
        this.last = page.isLast();
    }

    // Getters and Setters
}

Modify the controller to use this DTO:

@GetMapping("/products/custom")
public PaginatedResponse<Product> getCustomPaginatedProducts(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size,
        @RequestParam(defaultValue = "id") String sortBy,
        @RequestParam(defaultValue = "asc") String sortDir) {
    Page<Product> productPage = productService.getAllProducts(page, size, sortBy, sortDir);
    return new PaginatedResponse<>(productPage);
}

8. Efficient Pagination for Large Datasets

For large datasets, consider the following optimizations:

Use Indexes in the Database

Ensure the columns used in sorting and filtering are indexed to improve query performance.

Limit the Data Being Fetched

Fetch only the required columns using projections:

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface ProductRepository extends JpaRepository<Product, Long> {

    @Query("SELECT p.name, p.price FROM Product p WHERE p.category = :category")
    Page<Object[]> findProductNamesAndPricesByCategory(@Param("category") String category, Pageable pageable);
}

Use Keyset Pagination

For highly optimized pagination, use keyset pagination instead of offset pagination.

@Query("SELECT p FROM Product p WHERE p.price > :price ORDER BY p.price ASC")
List<Product> findProductsByPriceGreaterThan(@Param("price") Double price, Pageable pageable);

9. Testing the APIs

Use tools like Postman or cURL to test the endpoints.

Example Request:

curl -X GET "http://localhost:8080/products?page=0&size=5&sortBy=name&sortDir=desc"

Response:

{
  "content": [
    { "id": 1, "name": "Product1", "category": "Electronics", "price": 1000.0 },
    { "id": 2, "name": "Product2", "category": "Electronics", "price": 500.0 }
  ],
  "pageNumber": 0,
  "pageSize": 5,
  "totalElements": 20,
  "totalPages": 4,
  "last": false
}

10. Conclusion

This guide covered the complete implementation of pagination and sorting in Spring Data JPA. By leveraging Pageable, Sort, and custom responses, you can efficiently handle large datasets while maintaining scalability and performance.

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete