spring

Spring Data JPA Scroll API

The Scroll API offers filtering capabilities through both offset and keyset methods. Unlike offset-based filtering, which necessitates the retrieval of the entire result set from the database, keyset filtering is designed to optimize performance by constructing smaller, more efficient result sets, thereby reducing the computation and IO load on the database. Let us explore the Spring Data JPA Scroll API.

1. Introduction

The Scroll API represents a valuable enhancement within Spring Data JPA, catering to the requirement of handling extensive datasets without the burden of loading everything into memory. It facilitates the streamlined traversal of database records, akin to a streaming approach, effectively averting memory depletion issues when managing substantial result sets.

2. Use-case

Imagine situations where your application is tasked with managing a substantial volume of database records. Retrieving all these records simultaneously into memory can result in performance bottlenecks and an escalation in memory consumption. The Scroll API offers a remedy by enabling you to sequentially stream records, mitigating these concerns effectively.

3. Developing a Spring boot Scroll API application

Let’s delve into some practical exercises. Please note that for simplicity I will skip the main implementation class and focus on the code relevant to this article. In case anyone is interested in understanding the project structure:

Fig. 1: Project Structure

3.1 Setting up PostgreSQL on Docker

Usually, setting up the database is a tedious step but with Docker, it is a simple process. You can watch the video available at this link to understand the Docker installation on Windows OS. Once done open the terminal and trigger the below command to set and run postgresql.

Docker commands

-- command to run Postgres on docker –

-- Remember to change the password --
docker run -d -p 5433:5432 -e POSTGRES_PASSWORD= --name postgres postgres

-- command to stop the Postgres docker container --
docker stop postgres

-- command to remove the Postgres docker container --
docker rm postgres

Remember to enter the password of your choice. If everything goes well the postgresql database server will be up and running on a port number – 5433 and you can connect with the Dbeaver GUI tool to connect to the server.

Fig. 2: Postgres on Docker

3.1.1 Setting up pre-requisite data

To play with the application add the mock data to the database. You can use the below sql script to create a table car and insert mock data into it.

create table car (
    id SERIAL PRIMARY KEY,
	name VARCHAR(50),
	make VARCHAR(50),
	model_year VARCHAR(50)
);

insert into car (name, make, model_year) values ('Malibu', 'Chevrolet', 2004);
insert into car (name, make, model_year) values ('Silverado 2500', 'Chevrolet', 2008);
insert into car (name, make, model_year) values ('Reno', 'Suzuki', 2006);
insert into car (name, make, model_year) values ('Wrangler', 'Jeep', 2006);
insert into car (name, make, model_year) values ('LaCrosse', 'Buick', 2010);
insert into car (name, make, model_year) values ('Grand Vitara', 'Suzuki', 2010);
insert into car (name, make, model_year) values ('Rio5', 'Kia', 2008);
insert into car (name, make, model_year) values ('Ram Van 1500', 'Dodge', 2000);
insert into car (name, make, model_year) values ('4000CS Quattro', 'Audi', 1987);
insert into car (name, make, model_year) values ('MP4-12C', 'McLaren', 2012);
insert into car (name, make, model_year) values ('Chariot', 'Mitsubishi', 1995);
insert into car (name, make, model_year) values ('ZX2', 'Ford', 2001);
insert into car (name, make, model_year) values ('Grand Marquis', 'Mercury', 2003);
insert into car (name, make, model_year) values ('Equinox', 'Chevrolet', 2009);
insert into car (name, make, model_year) values ('Range Rover', 'Land Rover', 2005);
insert into car (name, make, model_year) values ('Corvette', 'Chevrolet', 2009);
insert into car (name, make, model_year) values ('62', 'Maybach', 2009);
insert into car (name, make, model_year) values ('8 Series', 'BMW', 1995);
insert into car (name, make, model_year) values ('Concorde', 'Chrysler', 1993);
insert into car (name, make, model_year) values ('Cutlass Supreme', 'Oldsmobile', 1992);
insert into car (name, make, model_year) values ('Freestar', 'Ford', 2004);
insert into car (name, make, model_year) values ('Laser', 'Ford', 1989);
insert into car (name, make, model_year) values ('Cougar', 'Mercury', 1997);
insert into car (name, make, model_year) values ('1 Series', 'BMW', 2009);
insert into car (name, make, model_year) values ('Suburban 2500', 'Chevrolet', 1993);
insert into car (name, make, model_year) values ('Summit', 'Eagle', 1994);
insert into car (name, make, model_year) values ('Quest', 'Nissan', 1998);
insert into car (name, make, model_year) values ('Enclave', 'Buick', 2011);
insert into car (name, make, model_year) values ('A6', 'Audi', 2010);
insert into car (name, make, model_year) values ('Aztek', 'Pontiac', 2003);
insert into car (name, make, model_year) values ('X-Type', 'Jaguar', 2006);
insert into car (name, make, model_year) values ('J', 'Infiniti', 1996);
insert into car (name, make, model_year) values ('F-Series', 'Ford', 1988);
insert into car (name, make, model_year) values ('Blazer', 'Chevrolet', 2004);
insert into car (name, make, model_year) values ('Ranger', 'Ford', 1986);
insert into car (name, make, model_year) values ('Sunbird', 'Pontiac', 1992);
insert into car (name, make, model_year) values ('Sonata', 'Hyundai', 2011);
insert into car (name, make, model_year) values ('ES', 'Lexus', 2011);
insert into car (name, make, model_year) values ('Town Car', 'Lincoln', 1991);
insert into car (name, make, model_year) values ('Protege', 'Mazda', 1993);
insert into car (name, make, model_year) values ('F150', 'Ford', 2001);
insert into car (name, make, model_year) values ('Mustang', 'Ford', 1966);
insert into car (name, make, model_year) values ('Eclipse', 'Mitsubishi', 2006);
insert into car (name, make, model_year) values ('Eclipse', 'Mitsubishi', 2011);
insert into car (name, make, model_year) values ('90', 'Audi', 1994);
insert into car (name, make, model_year) values ('A4', 'Audi', 2003);
insert into car (name, make, model_year) values ('CC', 'Volkswagen', 2013);
insert into car (name, make, model_year) values ('Jetta', 'Volkswagen', 1990);
insert into car (name, make, model_year) values ('LeBaron', 'Chrysler', 1993);
insert into car (name, make, model_year) values ('Seville', 'Cadillac', 1999);

3.2 Incorporating Dependencies into pom.xml

Establish a fresh Spring Boot project using Spring Initialzr or leverage an existing one. Include the necessary dependencies in your project’s pom.xml file.

pom.xml

<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>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<scope>runtime</scope>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>
</dependencies>

3.3 Configure Application Properties

Let us understand the application.properties.

  • server.port: Specifies the port number on which the server will listen. In this case, it’s set to 4444.
  • spring.datasource.url: Defines the URL for the PostgreSQL database to which your application will connect. It includes the host (localhost), port (5432), and the database name (mydatabase).
  • spring.datasource.username: Specifies the username used to authenticate with the PostgreSQL database. Here, it’s set to Postgres.
  • spring.datasource.password: Represents the password used for database authentication. In this case, it’s set to admin@1234.
  • spring.jpa.properties.hibernate.dialect: Indicates the Hibernate dialect for PostgreSQL, ensuring compatibility between Spring JPA and the database.
  • spring.jpa.hibernate.ddl-auto: Controls the behavior of Hibernate regarding database schema creation and modification. It’s set to update, meaning that Hibernate will automatically update the schema based on entity changes.
  • spring.jpa.show-sql: When set to true, this property instructs Spring JPA to log SQL statements executed by Hibernate for debugging and analysis purposes.
  • spring.jpa.open-in-view: Setting this property to false disables the “open-in-view” feature, which controls whether database connections are kept open throughout the entire request-response cycle. Disabling it can help prevent potential resource leaks.

application.properties

# spring property
server.port=4444

# spring jpa properties
spring.datasource.url=jdbc:postgresql://localhost:5432/mydatabase
spring.datasource.username=postgres
spring.datasource.password=admin@1234

spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

spring.jpa.open-in-view=false

3.4 Create a Model Class

The Java class Car serves as a model entity that encapsulates car-related information. Annotated with Java Persistence API (JPA) annotations, it establishes a mapping between the Java class and a corresponding database table. The @Entity annotation signifies its status as a JPA entity, signifying its instances’ connection to database rows. The @Table annotation specifies the table name as “car.” Furthermore, the @Id annotation designates the id field as the primary key, uniquely identifying each car record. Simple fields like name and make are implicitly mapped to corresponding database columns. The @Column annotation customizes the mapping for the modelYear field, linking it to a database column named “model_year.” Getters and setters are provided for property access, and a custom toString method furnishes a formatted string representation of a Car object. This class is integral in facilitating interactions between a Java application, a relational database, and Spring Data JPA, enabling CRUD operations on car records within the “car” table.

Car.java

package com.example.demo.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "car")
public class Car {

    @Id
    private int id;
    private String name;
    private String make;
    @Column(name = "model_year")
    private String modelYear;

    //getters and setters methods

    @Override
    public String toString() {
        return "Car{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", make='" + make + '\'' +
                ", modelYear='" + modelYear + '\'' +
                '}';
    }
}

3.5 Create a Repository Interface

Create an interface to interact with the database. The findFirst3ByModelYear method is a custom query method typically found in a Spring Data JPA repository interface. It serves the purpose of retrieving a window of up to three Car entities from a database based on the specified modelYear and provided OffsetScrollPosition. The modelYear parameter acts as a filter, allowing you to narrow down the results to Car entities with a specific model year. The OffsetScrollPosition position parameter determines the starting point or scroll position for fetching the data, and its type suggests it’s used for specifying an offset or the initial position when dealing with potentially large result sets. The method returns a Window object containing the selected Car entities. This approach is precious for implementing pagination or windowing in applications, as it enables data retrieval in manageable batches, improving performance and memory efficiency when working with extensive datasets. The OffsetScrollPosition parameter allows for efficient navigation through large sets of records.

CarRepository.java

package com.example.demo.repository;

import com.example.demo.model.Car;
import org.springframework.data.domain.OffsetScrollPosition;
import org.springframework.data.domain.Window;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CarRepository extends JpaRepository<Car, Integer> {

    Window<Car> findFirst3ByModelYear(String modelYear, OffsetScrollPosition position);
}

3.6 Create a Service Layer Class

The provided method, findCarsUsingOffsetFilteringAndWindowIterator, is designed to retrieve a list of cars based on a specified year while implementing a form of paging or windowing mechanism. It takes a single parameter, year representing the target year for car retrieval. The method employs a custom WindowIterator to abstract the process of fetching data in manageable chunks or windows. Inside the WindowIterator.of(...), there’s a lambda expression responsible for retrieving the next window of car data from the repository. This lambda invokes the repository.findFirst3ByModelYear(year, (OffsetScrollPosition) scrollPosition) method, which appears to fetch the first three cars for the given year, utilizing a scrollPosition to determine the starting point for the next window of data. The initial position is set using ScrollPosition.offset(). The method iterates through the windows, accumulating cars in an ArrayList named cars. Finally, it returns the cars list, which should contain all the cars retrieved from the database. This approach enables the retrieval of potentially large datasets in smaller, memory-efficient windows, improving performance when dealing with substantial data volumes.

CarService.java

package com.example.demo.service;

import com.example.demo.model.Car;
import com.example.demo.repository.CarRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.OffsetScrollPosition;
import org.springframework.data.domain.ScrollPosition;
import org.springframework.data.support.WindowIterator;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class CarService {

    @Autowired
    private CarRepository repository;

    public List<Car> findAll() {
        return repository.findAll();
    }

    public List<Car> findCarsUsingOffsetFilteringAndWindowIterator(String year) {
        final WindowIterator<Car> carWindowIterator =
                WindowIterator.of(scrollPosition -> repository.findFirst3ByModelYear(year, (OffsetScrollPosition) scrollPosition))
                        .startingAt(ScrollPosition.offset());

        final List<Car> cars = new ArrayList<>();
        carWindowIterator.forEachRemaining(cars::add);
        return cars;
    }
}

3.7 Create a Controller Class

The Java class CarController is a pivotal component in a Spring Boot application, serving as a RESTful web service controller for managing car-related data. Annotated with @RestController, it signifies its role in handling HTTP requests. The @RequestMapping("/api") annotation establishes a base URL path for this controller. Within the controller, an instance of CarService is injected using @Autowired, enabling access to car-related operations. Two HTTP GET endpoints are defined: "/api/all" and "/api/scrollapi". The former, mapped to @GetMapping("/all"), retrieves a list of all cars by invoking the findAll method of CarService. The latter, mapped to @GetMapping("/scrollapi"), calls the findCarsUsingOffsetFilteringAndWindowIterator method with the year “2009” as a parameter to retrieve car data with a specific year filtering. Both methods return results encapsulated in ResponseEntity objects, ensuring appropriate HTTP status codes for successful responses. This CarController plays a pivotal role in exposing car-related functionalities via RESTful web services in the application.

CarController.java

package com.example.demo.controller;

import com.example.demo.model.Car;
import com.example.demo.service.CarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api")
public class CarController {

    @Autowired
    private CarService service;

    // HTTP GET - http://localhost:4444/api/all
    @GetMapping("/all")
    public ResponseEntity<List<Car>> findAll() {
        return ResponseEntity.ok(service.findAll());
    }

    // HTTP GET - http://localhost:4444/api/scrollapi
    @GetMapping("/scrollapi")
    public ResponseEntity<List<Car>> findByScrollApi() {
        return ResponseEntity.ok(service.findCarsUsingOffsetFilteringAndWindowIterator("2009"));
    }
}

3.8 Run the Application

Now you can run your Spring Boot application via the main class DemoApplication.java. You can test the different endpoints using tools like curl or a REST client like the Postman tool.

Curl Requests

-- GET all cars
http://localhost:4444/api/all

-- GET car using scroll api
http://localhost:4444/api/scrollapi

4. Conclusion

In conclusion, leveraging the Scroll API within a Spring Boot application provides valuable benefits, primarily through the implementation of pagination. Pagination offers a range of advantages that enhance the application’s performance, usability, and scalability. Firstly, it significantly boosts performance by enabling the retrieval and display of data in smaller, more manageable portions, reducing database and server load and ensuring faster response times. Secondly, pagination conserves system resources by loading only the necessary data, making the application more memory-efficient and capable of handling extensive datasets without memory issues.

Moreover, it greatly enhances the user experience by facilitating seamless navigation through data, allowing users to swiftly access the specific information they seek. Additionally, as an application grows and accumulates more data, pagination becomes vital for maintaining consistent performance. Its division of data into pages ensures continued responsiveness, even with large and expanding databases. Furthermore, pagination minimizes network latency by reducing data transferred over the network, a crucial aspect when dealing with remote databases or distributed systems.

Customization options such as setting the number of items per page, sorting criteria, and filtering further tailor the user experience. Additionally, pagination can positively impact SEO, as it aids search engines in efficiently indexing paginated content, potentially enhancing a website’s search result visibility. It also promotes data integrity by processing data in smaller batches, reducing the risk of data inconsistencies and conflicts. In Spring Boot, implementing pagination with the Scroll API is straightforward, although striking a balance between page size and the number of pages is essential to avoid overwhelming the database. In summary, Spring Boot’s Scroll API and pagination offer an effective means of managing and presenting large datasets, resulting in improved performance, resource efficiency, and user satisfaction, all while ensuring scalability and responsiveness as data volumes increase.

5. Download the Project

This was a tutorial to understand Scroll API in Spring boot. You can download the source code from the below link.

Download
You can download the full source code of this example here: Spring Data JPA Scroll API

Yatin

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button