Batch Request Merging in Spring Boot to Reduce Database Connections

This article demonstrates how to merge multiple user‑info requests on the server side using a blocking queue, ScheduledThreadPoolExecutor, and CompletableFuture in Spring Boot, thereby consolidating SQL queries into a single batch call to save database connection resources while handling high concurrency.

Architect
Architect
Architect
Batch Request Merging in Spring Boot to Reduce Database Connections

The article explains the problem of multiple concurrent requests each opening a separate database connection, which wastes resources, and proposes merging these requests into a single batch SQL query on the server side.

Technical Means

LinkedBlockingQueue

– a blocking queue used to collect incoming requests. ScheduledThreadPoolExecutor – a scheduled thread pool that periodically processes the queued requests. CompletableFuture – used to return results asynchronously (originally without timeout support).

Code Implementation

Query User Service Interface

public interface UserService {
    Map<String, Users> queryUserByIdBatch(List<UserWrapBatchService.Request> userReqs);
}

User Service Implementation

@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UsersMapper usersMapper;

    @Override
    public Map<String, Users> queryUserByIdBatch(List<UserWrapBatchService.Request> userReqs) {
        List<Long> userIds = userReqs.stream()
            .map(UserWrapBatchService.Request::getUserId)
            .collect(Collectors.toList());
        QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("id", userIds);
        List<Users> users = usersMapper.selectList(queryWrapper);
        Map<Long, List<Users>> userGroup = users.stream()
            .collect(Collectors.groupingBy(Users::getId));
        HashMap<String, Users> result = new HashMap<>();
        userReqs.forEach(val -> {
            List<Users> usersList = userGroup.get(val.getUserId());
            if (!CollectionUtils.isEmpty(usersList)) {
                result.put(val.getRequestId(), usersList.get(0));
            } else {
                result.put(val.getRequestId(), null);
            }
        });
        return result;
    }
}

Batch Request Service

@Service
public class UserWrapBatchService {
    @Resource
    private UserService userService;
    public static int MAX_TASK_NUM = 100;
    private final Queue<Request> queue = new LinkedBlockingQueue();

    @PostConstruct
    public void init() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            int size = queue.size();
            if (size == 0) return;
            List<Request> list = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                if (i < MAX_TASK_NUM) {
                    list.add(queue.poll());
                }
            }
            List<Request> userReqs = new ArrayList<>();
            for (Request request : list) {
                userReqs.add(request);
            }
            Map<String, Users> response = userService.queryUserByIdBatch(userReqs);
            for (Request request : list) {
                Users result = response.get(request.requestId);
                request.completableFuture.complete(result);
            }
        }, 100, 10, TimeUnit.MILLISECONDS);
    }

    public Users queryUser(Long userId) {
        Request request = new Request();
        request.requestId = UUID.randomUUID().toString().replace("-", "");
        request.userId = userId;
        CompletableFuture<Users> future = new CompletableFuture<>();
        request.completableFuture = future;
        queue.offer(request);
        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return null;
    }

    public class Request {
        String requestId;
        Long userId;
        CompletableFuture<Users> completableFuture;
        // getters and setters omitted for brevity
    }
}

Controller Invocation

@RequestMapping("/merge")
public Callable<Users> merge(Long userId) {
    return () -> userBatchService.queryUser(userId);
}

A high‑concurrency test using 30 threads demonstrates that the server merges dozens of requests into a single SQL call, significantly reducing the number of database connections.

Important Issues

Java 8's CompletableFuture lacks a built‑in timeout mechanism.

The generated SQL has length limits, so the batch size must be capped (the example uses MAX_TASK_NUM).

Using Queue with Timeout to Solve CompletableFuture Timeout Limitation

The article introduces an alternative implementation that replaces CompletableFuture with a LinkedBlockingQueue that supports timed polling, allowing a 3‑second timeout for each request.

@Service
public class UserWrapBatchQueueService {
    // similar structure to UserWrapBatchService but uses LinkedBlockingQueue<Users> usersQueue
    // request method polls the queue with a timeout of 3000 ms
}

Conclusion

Request merging and batch processing can dramatically save connection resources for databases or remote services, though it introduces a small waiting latency and is therefore unsuitable for low‑concurrency scenarios.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Batch ProcessingCompletableFutureSpring BootJava concurrencylinkedblockingqueuerequest merging
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.