Completable Future To Fetch Data Asynchronously

Asynchronous Data

Here I am going to show you how to fetch data asynchronously. So, data which are not coordinated in time will be fetched any time. This means that data is sent at irregular intervals, without any specific time or synchronization between the sender and receiver.

In this example I am going to use CompletableFuture from Java 8’s new API. I am going to fetch data asynchronously from database here and the data can be fetched asynchronously from any source. I am using Spring Data JPA API to fetch data from MySQL database.

Let’s say the following piece of code fetches data from database:

qEngine.select("select emp from employee emp").forEach(emp -> System.out.println(emp));

The above query simply fetches data from the database using Java Persistence Query Language and prints the result. This type of query will be slow due to synchronous nature of the request. So you may need to execute the task in a separate thread and trigger the printing of result when it is available, because you may not need to take care of such task once it is launched.

You can execute this task using Callable and Future by submitting the task to ExecutorService.

Callable<String> task = () -> "select emp from employee";

Future<String> future = executorService.submit(task);

The only way to get the result from future object is to call its get() method. But this method call will block the thread until the result is available for printing.

This is where the CompletableFuture comes to the rescue. Let’s rewrite the submission task using CompletableFuture.

Therefore,

executor.submit(() -> {
    () -> "select emp from employee";
});

Becomes the following:

CompletableFuture<List<Employee>> completableFuture =
    CompletableFuture.supplyAsync(() -> {    
		() -> dEngine.query("select emp from employee");    
	}, executor);

Instead of passing our Callable to the submit() method of an ExecutorService, you pass it to the static supplyAsync() method of CompletableFuture. This method also takes an Executor as a second parameter, giving the client a choice for the pool of threads that is going to execute the Callable.

It returns an instance of CompletableFuture. On this object, you can call the following:

completableFuture.thenAccept(System.out::println);

The consumer passed to the thenAccept() method will be called automatically, without any client intervention, when the result is available. In this case no more thread blocking as in the previous case.

Prerequisites

Java 19, Spring Boot 3.1.3, MySQL 8.1.0, Maven 3.8.5

Project Setup

The following pom.xml can be used for your maven based project:

<?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.roytuts</groupId>
	<artifactId>completablefuture-fetch-data-asynchronously</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.3</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>19</maven.compiler.source>
		<maven.compiler.target>19</maven.compiler.target>
	</properties>

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

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

		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

MySQL Tables and Data

The following tables are created under roytuts database in the MySQL database server.

CREATE DATABASE IF NOT EXISTS `roytuts`;
USE `roytuts`;

CREATE TABLE IF NOT EXISTS `person` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `firstName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `lastName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1007 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `person` (`id`, `firstName`, `lastName`) VALUES
	(1000, 'Soumitra', 'Roy'),
	(1001, 'Souvik', 'Sanyal'),
	(1002, 'Arup', 'Chatterjee'),
	(1003, 'Suman', 'Mukherjee'),
	(1004, 'Debina', 'Guha'),
	(1005, 'Liton', 'Sarkar'),
	(1006, 'Debabrata', 'Poddar');
	
CREATE TABLE IF NOT EXISTS `product` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `price` double NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `product` (`id`, `name`, `code`, `price`) VALUES
	(1, 'American Tourist', 'AMTR01', 12000),
	(2, 'EXP Portable Hard Drive', 'USB02', 5000),
	(3, 'Shoes', 'SH03', 1000),
	(4, 'XP 1155 Intel Core Laptop', 'LPN4', 80000),
	(5, 'FinePix Pro2 3D Camera', '3DCAM01', 150000),
	(6, 'Simple Mobile', 'MB06', 3000),
	(7, 'Luxury Ultra thin Wrist Watch', 'WristWear03', 3000),
	(8, 'Headphone', 'HD08', 400);

Application Config

The following datasource configuration is kept into the application.properties file under class path folder src/main/resources.

#datasource
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/roytuts
spring.datasource.username=root
spring.datasource.password=root

#schema generation from Hibernate
spring.jpa.hibernate.ddl-auto=none
#spring.jpa.hibernate.ddl-auto=create

#spring data jpa adding underscore to table column's name though column name does not have underscore
#and exact table column name was provided using, for example, @Column(name = "firstName") on the Java field
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

The spring.jpa.hibernate.naming.physical-strategy is added to prevent spring data jpa from adding underscore to the table’s column name automatically so that spring does not throw exception while reading the column name from the table.

Entity Class

The following entity class is used to map the person table in the database server.

@Entity
@Table(name = "person")
public class Person implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@Column
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private long id;

	@Column(name = "firstName")
	private String firstName;

	@Column(name = "lastName")
	private String lastName;

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	@Override
	public String toString() {
		return "Person [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + "]";
	}

}

The following entity class is used to map the product table in the database server.

@Entity
@Table(name = "product")
public class Product implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@Column
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private long id;

	@Column
	private String name;

	@Column
	private String code;

	@Column
	private double price;

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}

	@Override
	public String toString() {
		return "Product [id=" + id + ", name=" + name + ", code=" + code + ", price=" + price + "]";
	}

}

Repository

The repository interface is used to query person table. The spring data jpa repository API provides built-in methods for ready-made CRUD operations.

public interface PersonRepository extends JpaRepository<Person, Integer> {

}

Similarly I have created the ProductRepository to query the product table using spring data jpa API.

public interface ProductRepository extends JpaRepository<Product, Integer> {

}

Service

The following service class is used to process business logic for the application though here simply I am fetching data from the table.

In this service class I am also using CompletableFuture‘s supplyAsync() method to query database table asynchronously.

@Service
public class PersonProductService {

	@Autowired
	private PersonRepository personRepository;

	@Autowired
	private ProductRepository productRepository;

	public void printPersons() throws InterruptedException, ExecutionException {
		System.out.println("Person Details");
		System.out.println("=========================================");
		
		CompletableFuture
		.supplyAsync(() -> personRepository.findAll()).thenAccept(System.out::println);

		//CompletableFuture<List<Person>> completableFuture = CompletableFuture
			//	.supplyAsync(() -> personRepository.findAll());
		//completableFuture.thenAccept(System.out::println);
		System.out.println();
	}

	public List<Product> getProducts() throws InterruptedException, ExecutionException {
		return CompletableFuture.supplyAsync(() -> productRepository.findAll()).get();
	}

}

After fetching data asynchronously from person table, I am simply printing the result in the service class. But for product table, after fetching data asynchronously I am sending data to the receiver.

Spring Boot Main Class

The following spring boot main class will deploy the application in CLI mode.

@SpringBootApplication
public class App implements CommandLineRunner {

	@Autowired
	private PersonProductService personProductService;

	public static void main(String[] args) {
		SpringApplication.run(App.class, args).close();
	}

	@Override
	public void run(String... args) throws Exception {
		personProductService.printPersons();

		System.out.println("Product Details");
		System.out.println("=========================================");
		personProductService.getProducts().forEach(p -> System.out.println(p));
	}

}

Results

Running the above main class will give you the following results in the console:

Person Details
=========================================

Product Details
=========================================
[Person [id=1000, firstName=Soumitra, lastName=Roy], Person [id=1001, firstName=Souvik, lastName=Sanyal], Person [id=1002, firstName=Arup, lastName=Chatterjee], Person [id=1003, firstName=Suman, lastName=Mukherjee], Person [id=1004, firstName=Debina, lastName=Guha], Person [id=1005, firstName=Liton, lastName=Sarkar], Person [id=1006, firstName=Debabrata, lastName=Poddar]]
Product [id=1, name=American Tourist, code=AMTR01, price=12000.0]
Product [id=2, name=EXP Portable Hard Drive, code=USB02, price=5000.0]
Product [id=3, name=Shoes, code=SH03, price=1000.0]
Product [id=4, name=XP 1155 Intel Core Laptop, code=LPN4, price=80000.0]
Product [id=5, name=FinePix Pro2 3D Camera, code=3DCAM01, price=150000.0]
Product [id=6, name=Simple Mobile, code=MB06, price=3000.0]
Product [id=7, name=Luxury Ultra thin Wrist Watch, code=WristWear03, price=3000.0]
Product [id=8, name=Headphone, code=HD08, price=400.0]

If you look at the above results, you will see that the Product Details heading has been printed right after the Person Details heading and then all person details followed by all product details. Therefore, due to asynchronous data fetching the person details were not available immediately.

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *