Backend Development 15 min read

Merging Backend Requests in SpringBoot to Reduce Database Connections

This article explains how to merge multiple backend requests in a SpringBoot application using a blocking queue, ScheduledThreadPoolExecutor, and CompletableFuture to batch database queries, reduce connection overhead, handle high concurrency, and includes full Java code examples and performance testing.

Top Architect
Top Architect
Top Architect
Merging Backend Requests in SpringBoot to Reduce Database Connections

The author, a senior architect, demonstrates the significance of request merging to save valuable database connection resources by consolidating multiple user queries into a single SQL statement.

Technical Means

LinkedBlockingQueue – a blocking queue for request aggregation.

ScheduledThreadPoolExecutor – a scheduled thread pool that periodically processes the queued requests.

CompletableFuture – used to return results asynchronously (note: Java 8 CompletableFuture lacks a timeout mechanism, which is later addressed).

Code Implementation

Query Service Interface

public interface UserService {
    Map
queryUserByIdBatch(List
userReqs);
}

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

    @Override
    public Map
queryUserByIdBatch(List
userReqs) {
        List
userIds = userReqs.stream().map(UserWrapBatchService.Request::getUserId).collect(Collectors.toList());
        QueryWrapper
queryWrapper = new QueryWrapper<>();
        queryWrapper.in("id", userIds);
        List
users = usersMapper.selectList(queryWrapper);
        Map
> userGroup = users.stream().collect(Collectors.groupingBy(Users::getId));
        HashMap
result = new HashMap<>();
        userReqs.forEach(val -> {
            List
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

package com.springboot.sample.service.impl;

import com.springboot.sample.bean.Users;
import com.springboot.sample.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.*;

@Service
public class UserWrapBatchService {
    @Resource
    private UserService userService;

    public static int MAX_TASK_NUM = 100;

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

    private final Queue
queue = new LinkedBlockingQueue();

    @PostConstruct
    public void init() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            int size = queue.size();
            if (size == 0) return;
            List
list = new ArrayList<>();
            for (int i = 0; i < size && i < MAX_TASK_NUM; i++) {
                list.add(queue.poll());
            }
            List
userReqs = new ArrayList<>(list);
            Map
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
future = new CompletableFuture<>();
        request.completableFuture = future;
        queue.offer(request);
        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Controller Invocation

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

High‑Concurrency Test

package com.springboot.sample;

import org.springframework.web.client.RestTemplate;
import java.util.Random;
import java.util.concurrent.CountDownLatch;

public class TestBatch {
    private static int threadCount = 30;
    private static final CountDownLatch COUNT_DOWN_LATCH = new CountDownLatch(threadCount);
    private static final RestTemplate restTemplate = new RestTemplate();

    public static void main(String[] args) {
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                COUNT_DOWN_LATCH.countDown();
                try { COUNT_DOWN_LATCH.await(); } catch (InterruptedException e) { e.printStackTrace(); }
                for (int j = 1; j <= 3; j++) {
                    int param = new Random().nextInt(4);
                    if (param <= 0) param++;
                    String response = restTemplate.getForObject("http://localhost:8080/asyncAndMerge/merge?userId=" + param, String.class);
                    System.out.println(Thread.currentThread().getName() + " param " + param + " response " + response);
                }
            }).start();
        }
    }
}

Conclusion

Request merging and batch processing can dramatically reduce the number of database connections or remote‑service calls, improving resource utilization in high‑concurrency scenarios, though it adds a small waiting latency and may not suit low‑traffic workloads.

batch processingSpringBootbackend optimizationJava Concurrencyrequest merging
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login 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.