Async with CompletableFuture
In JDK 1.8, the Future object got an upgrade and it became
a CompletableFuture object which besides Future, also
implements CompletionStage. The CompletionStage offers
a lot of methods for a simplified usage of the responses
computed in different threads and stages. Some of the
most common are thenApply() similar with map() from
Streams, thenAccept() similar with foreach. There are
multiple ways to get a CompletableFuture response, some
of them running the task on a different thread or not, but
one common point and extra functionality from Futures is
that users can treat exceptions if they occur during
computation.
Async with @Async
Another way to implement async is to use the
annotation @Async provided by Spring Framework. Users
can use it only on public methods and can’t call the
methods from the same class where they are defined. Any
code that is inside a method annotated with Async will be
executed in a different thread and can be void or can
return a CompletableFuture. So this an alternative to
creating a CompletableFuture and giving it a method to
run, but in order to be able to use this annotation, another
thing is needed: @EnableAsync on a configuration class.
Pros and cons of asynchronous
Users should think about using asynchronous
programming when wanting to delegate a task to another
thread because it is time consuming or they don’t want the
result of a task to impact the current flow of the
application. In this way users can do multiple things at
once. By using asynchronous users can decouple tasks and
components and it results in a better overall performance
of the application.
Alternatively, users should be aware that debugging is a
little more complicated in a code with asynchronous
methods and even writing tests, but this shouldn’t be an
impediment when choosing a solution.
One last thing
Threads are about workers and async is about tasks!
Certainly! Here's how you can integrate asynchronous processing, CompletableFuture, and Spring
Batch to improve the execution time of your CSV upload operation:
Async Operation with CompletableFuture:
Use @Async annotation in your service method to make it asynchronous.
Wrap the processing logic inside a CompletableFuture to perform the upload operation
asynchronously.
Return a CompletableFuture<Boolean> to indicate the success or failure of the upload operation.
java
Copy code
import [Link];
import [Link];
import [Link];
import [Link];
@Service
public class StockService {
@Async
public CompletableFuture<Boolean> uploadCSVAsync(MultipartFile file) {
// Async processing logic here
Spring Batch:
Use Spring Batch to process large volumes of data efficiently.
Define a job with steps to read, process, and write data from the CSV file to the database.
Configure chunk-based processing to process data in chunks, reducing memory consumption and
improving performance.
java
Copy code
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Configuration
@EnableBatchProcessing
public class BatchConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Job csvUploadJob() {
return [Link]("csvUploadJob")
.start(csvUploadStep())
.build();
}
@Bean
public Step csvUploadStep() {
return [Link]("csvUploadStep")
.<MultipartFile, Boolean>chunk(10) // Process 10 items at a time
.reader(csvItemReader())
.processor(csvItemProcessor())
.writer(csvItemWriter())
.build();
// Define CSV reader, processor, and writer beans here
Integration:
Trigger the Spring Batch job asynchronously from your controller or service method using
CompletableFuture.
Monitor the completion of the job and handle the response accordingly.
java
Copy code
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
@Controller
public class TestController {
@Autowired
private StockService stockService;
@PostMapping("/upload-csv")
public ResponseEntity<Map<String, Object>> uploadCSV(@RequestParam("file") MultipartFile file)
{
Map<String, Object> response = new HashMap<>();
try {
if ([Link]()) {
[Link]("success", false);
[Link]("message", "Please select a file to upload.");
return [Link]().body(response);
CompletableFuture<Boolean> uploadResult = [Link](file);
[Link](success -> {
if (success) {
[Link]("success", true);
[Link]("message", "File uploaded successfully!");
} else {
[Link]("success", false);
[Link]("message", "Failed to process the file.");
});
return [Link]().body(response);
} catch (Exception e) {
[Link]("success", false);
[Link]("message", "An error occurred while uploading the file.");
return [Link](HttpStatus.INTERNAL_SERVER_ERROR).body(response);
}
}
By leveraging asynchronous processing with CompletableFuture and integrating Spring Batch for
batch processing, you can significantly improve the execution time and scalability of your CSV upload
operation.
Today
@Service
public class StockService {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private Job job;
public void uploadCSV(MultipartFile file) {
try {
// Save the uploaded file temporarily
byte[] bytes = [Link]();
Path path = [Link]("[Link]");
[Link](path, bytes);
// Trigger Spring Batch job to process the uploaded file
[Link](job, new JobParametersBuilder()
.addString("filePath", [Link]().toString())
.toJobParameters());
// Delete the temporary file after processing
[Link](path);
} catch (Exception e) {
// Handle exception
[Link]();