Skip to content

Latest commit

 

History

History
109 lines (78 loc) · 3.53 KB

README-java-asynchronous-environments-EN.md

File metadata and controls

109 lines (78 loc) · 3.53 KB

🖥️ Sharing QueryCountPerRequest Across Threads in Asynchronous Environments

한국어 | English

🖥️ Overview

The Multi-Datasource-Query-Counter library counts database queries per API request. By default, it uses @RequestScope to ensure each request has its own counter. However, in asynchronous environments (CompletableFuture, @Async, WebFlux, etc.), execution switches to new threads, which cannot access the original request context, resulting in missed query counts.

This guide explains how to properly share the QueryCountPerRequest object across threads in asynchronous environments.


🖥️ The Problem

In a typical Spring application, a request is processed within a single thread. However, when using asynchronous code:

  1. Execution switches to a new thread.
  2. The new thread has a new context.

So, the new thread cannot access the original request context.


🖥️ Solution: Using TaskDecorator

Spring provides TaskDecorator which allows copying the request context to new threads before executing asynchronous tasks.

1. Configure ThreadPoolTaskExecutor

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.concurrent.Executor;

@EnableAsync
@Configuration
public class AsyncConfig {
    
    @Bean(name = "asyncExecutor")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        
        // Configure thread pool settings according to your application needs... (skip)

        executor.setTaskDecorator(task -> {
            // Get the QueryCountPerRequest from the current thread
            QueryCountPerRequest currentQueryCountPerRequest = QueryCountPerRequestHolder.INSTANCE.get();

            return () -> {
                try {
                    // Propagate the QueryCountPerRequest to the new thread
                    if (currentQueryCountPerRequest != null) {
                        QueryCountPerRequestHolder.INSTANCE.set(currentQueryCountPerRequest);
                    }
                    task.run();
                } finally {
                    // Clear the QueryCountPerRequest after the task is complete
                    QueryCountPerRequestHolder.INSTANCE.remove();
                }
            };
        });
        
        executor.initialize();
        return executor;
    }
}

2. Using CompletableFuture

When using CompletableFuture directly, use the custom Executor:

@Autowired
@Qualifier("asyncExecutor")
private Executor asyncExecutor;

public CompletableFuture<List<User>> findAllUsersAsync() {
    return CompletableFuture.supplyAsync(() -> {
        return userRepository.findAll(); // Query count point.
    }, asyncExecutor);                   // Specify the custom executor.
}

3. Using @Async Annotation

When using the @Async annotation, explicitly specify the configured Executor:

@Async("asyncExecutor")  // Specify the bean name explicitly.
public CompletableFuture<User> findUserByIdAsync(Long id) {
    return CompletableFuture.completedFuture(
        userRepository.findById(id).orElse(null) // Query count point.
    );
}

🖥️ WebFlux/Reactor Environment

(WIP)