Pagination in Spring Boot REST APIs with H2 Database

Pagination in Spring Boot REST APIs with H2 Database

Table of contents

In this article, you are going to learn how to create pagination in the spring Boot REST API and how you can use it in your project. The code is very generic so you can use any RDBMS instead of the H2 database. For simplicity, I have used the H2 database.

Let us begin and start coding, we open Spring Initializr from start.spring.io and add the below dependencies. Click on Generate and after the project downloads, we open it in our IDE/editor of choice (I am using VSCode). You can use vscode to generate spring boot project as well.

image.png

and the project structure looks like below.

image.png

Let's start coding.

application.properties:

In application properties, we have added data source properties and the spring boot application name.


spring.application.name=pagination-springboot
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:customer
spring.datasource.username=sa
spring.datasource.password=sa
spring.datasource.driver-class-name=org.h2.Driver
server.port=9010

Customer.java Let us create an entity that helps to represent the same structure as a table. We try to demonstrate things with minimal fields so that we can concentrate on pagination.

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.util.Date;
import java.util.UUID;

@Entity
@Table(name="customer")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Customer{

    @Id
    @GeneratedValue()
    private UUID id;

    @Column(nullable = false)
    private String userId;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String mobileno;

    private Date createdDate;

    @Column(nullable = false)
    private String createdBy;

    private Date updatedDate;

}

CustomerDTO.java Let us create customerDTO that helps to transfer data.

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.UUID;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CustomerDTO {

    private UUID id;

    private String userId;

    @NotNull(message = "name cannot be empty")
    private String name;

   @NotNull(message="mobile number cannot be empty")
    private String mobileno;

    private Date createdDate;

    private Date updatedDate;

    private String createdBy;

}

CustomerRespositoy.java Let us create a repository interface by extending JpaRepository that helps to do CRUD operations on a database

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

import java.util.List;
import java.util.Optional;
import java.util.UUID;

public interface CustomerRepository extends JpaRepository <Customer, UUID> {

    Optional<List<Customer>> findAllByUserId(String userId, Pageable page);
}

CustomerService.java

The saveCustomer() saves new customer records. We call Spring Data JPAs save() method to save a new customer. The getAllCustomers() retrieves all customers from the database. We pass an argument of type Pageable when we make a call to Spring Data JPA’s findAll() method.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@Transactional
public class CustomerService {

    @Autowired
    CustomerRepository customerRepository;

    public void saveCustomer(CustomerDTO customerDTO) {
        Customer customer= Customer
                .builder()
                .name(customerDTO.getName())
                .mobileno(customerDTO.getMobileno())
                .createdDate(new Date())
                .createdBy(customerDTO.getCreatedBy())
                .userId(customerDTO.getUserId())
                .build();
        customerRepository.save(customer);
    }

    public List<CustomerDTO> getAllCustomers(Pageable paging){

        Iterable<Customer> customers= 
                customerRepository.findAll(paging);
        List<CustomerDTO> customersDTO = new ArrayList<>();

        customers.forEach( customer -> {
            customersDTO.add(CustomerDTO
                    .builder()
                    .id(customer.getId())
                    .userId(customer.getUserId())
                    .name(customer.getName())
                    .mobileno(customer.getMobileno())
                    .createdBy(customer.getCreatedBy())
                    .createdDate(customer.getCreatedDate())
                    .build());
        });

        return customersDTO;
    }


public List<CustomerDTO> getCustomersByUserId(
            String userId, 
            Pageable page) {
       Optional<List<Customer>> customers =  
      customerRepository.findAllByUserId(userId, page);

      if (customers.isPresent()) {
          return customers.get()
                   .stream()
                   .map( customer ->  CustomerDTO.builder()
                       .id(customer.getId())
                       .name(customer.getName())
                       .mobileno(customer.getMobileno())
                       .userId(customer.getUserId())
                       .createdBy(customer.getCreatedBy())
                       .createdDate(customer.getCreatedDate())
                       .build())
                   .collect(Collectors.toList());

       }
       return null;
    }
}

CustomerController.java Controller class that controls the request and response. The controller class is annotated with RestController and @RequestMapping(“/customers”).

The createCustomer() method saves new customer to the database and the getCustomers()method retrieves all customers. We define two parameters annotated with @RequestParams called page and pageSize.

The page parameter determines which page is to be returned as part of pagination. The first page is considered to be 0, and then incremented by one for subsequent pages. The default value is 0, which is the first page.

The pageSize parameter determines how many records will be returned for that particular page. We set a default value of 5. Both page and pageSize are optional, which means that if not present in our API request, the default values will be considered. You can define a Pageable variable with the page and pageSize parameters. This variable is used to query our database with the pagination details that you have to pass in the RequestParams annotated parameters page and pageSize.

Pageable paging = PageRequest.of(page, pageSize);

The final method getCustomersByUser() retrieves all customers made by a user. This method also has pagination implemented similar to the getCustomers() method.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.Date;
import java.util.List;

@RestController
@RequestMapping("/customers")
@CrossOrigin(origins = "http://localhost:3000/")
public class CustomersController {


    @Autowired
    CustomerService customerService;

    @PostMapping(
            consumes = {
                    MediaType.APPLICATION_JSON_VALUE ,
                    MediaType.APPLICATION_XML_VALUE},
            produces= {
                    MediaType.APPLICATION_JSON_VALUE ,
                    MediaType.APPLICATION_XML_VALUE
            })
    public ResponseEntity<CustomerDTO> creatCustomer(
     @Valid @RequestBody CustomerDTO customerDTO
    ) throws Exception {

        customerDTO.setCreatedDate(new Date());
        customerService.saveCustomer(customerDTO);

        return new ResponseEntity<>(customerDTO, HttpStatus.CREATED);
    }

    @GetMapping(
            consumes = {
                    MediaType.APPLICATION_JSON_VALUE ,
                    MediaType.APPLICATION_XML_VALUE},
            produces= {
                    MediaType.APPLICATION_JSON_VALUE ,
                    MediaType.APPLICATION_XML_VALUE
            })
    public ResponseEntity<List<CustomerDTO>> getCustomers(
           @RequestParam(defaultValue = "5", required = false) 
                  Integer pageSize,
           @RequestParam(defaultValue = "0", required = false) 
                  Integer page
    ) throws Exception {

        Pageable paging  = PageRequest.of(page, pageSize);

        List<CustomerDTO> customersDTO=   
              customerService.getAllCustomers(paging);

        return new ResponseEntity<>(
              customersDTO, HttpStatus.CREATED);
    }

    @GetMapping(
            path = "/user/{userId}",
            consumes = {
                    MediaType.APPLICATION_JSON_VALUE ,
                    MediaType.APPLICATION_XML_VALUE
            },
            produces = {
                    MediaType.APPLICATION_JSON_VALUE,
                    MediaType.APPLICATION_XML_VALUE
            })
    public ResponseEntity<List<CustomerDTO>> getCustomersByUserId(
            @PathVariable String userId,
            @RequestParam(defaultValue= "0", required = false)
                   Integer page ,
            @RequestParam(defaultValue= "5", required = false) 
                   Integer pageSize 
   ) throws Exception {
        Pageable paging = PageRequest.of(page, pageSize);

        List<CustomerDTO> customersDTOs= 
               customerService.getCustomersByUserId(userId, paging);

        return new ResponseEntity<List<CustomerDTO>>( 
               customersDTOs, HttpStatus.ACCEPTED);
    }

}

Now let us run the application and hit the URL localhost:9010. Let us verify the database by accessing h2 console by hitting the http://localhost:9010/h2-console and logging in by the credentials provided in the properties file.

image.png

Now let's push some records using the postman tool or any tool of your choice,

image.png

Insert some records in the table by using the post endpoint and verify by running a select query like below.

image.png

Pagination Retrieve records: Retrieve records using the GET HTTP verb with same URL endpoint.

image.png

If we make the below API GET request, we can see that five customers are returned. The default values of page=0 and pageSize=5. Hence it returns five records even though we have mentioned any page size or page number. If you provide the next page size in the request param then you will get the next page. The best thing about this implementation is that you can provide page size based on client requirements some may provide 10 or 100 or any number that suits.

localhost:9010/customers?page=1&pageSiz...

If records are not available on that page then it will return the empty JSON array.

Conclusion:

In this article, we have discussed pagination in spring boot REST API, an easy way we can pagination implementation with a database, you can same code to have pagination on any database such as MySQL, Oracle or PostgreSQL, etc.

Source Code:

$ git  clone https://github.com/maheshwarLigade/spring-boot-examples/tree/main/h2paginationdemo

cd h2paginationdemo

gradle build

More such articles:

https://medium.com/techwasti

https://www.youtube.com/channel/UCiTaHm1AYqMS4F4L9zyO7qA

https://www.techwasti.com/

==========================**=========================

If this article adds any value for you then please clap and comment.

Let’s connect on Stackoverflow, LinkedIn, & Twitter.

Did you find this article valuable?

Support techwasti by becoming a sponsor. Any amount is appreciated!