Boost Java Backend Performance: Combine MyBatis-Plus LambdaQueryWrapper with Spring @Async

Learn how to leverage MyBatis-Plus’s type-safe LambdaQueryWrapper together with Spring Boot’s @Async annotation to build efficient, asynchronous database operations, covering basic usage, advanced thread-pool configuration, exception handling, transaction management, and real-world scenarios for backend developers.

JD Tech Talk
JD Tech Talk
JD Tech Talk
Boost Java Backend Performance: Combine MyBatis-Plus LambdaQueryWrapper with Spring @Async

Article Background

While testing business requirements, I read the code specified by the R&D team and discovered several advanced techniques, such as MyBatis-Plus LambdaQueryWrapper and Spring Boot @Async, which initially seemed confusing.

image.png
image.png

Introduction to MyBatis-Plus LambdaQueryWrapper

LambdaQueryWrapper is a type‑safe query builder provided by MyBatis‑Plus. It leverages Java 8 lambda expressions to avoid hard‑coded field names, improving code readability and maintainability.

Basic Usage Example

LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getName, "张三")
            .ge(User::getAge, 18)
            .orderByDesc(User::getCreateTime);
List<User> userList = userMapper.selectList(queryWrapper);

Advantages of LambdaQueryWrapper

Type safety: field references are checked by the compiler.

High readability: fluent chaining makes intent clear.

SQL‑injection protection: parameters are bound automatically.

Smart IDE assistance: automatic field name completion.

Spring Boot @Async Asynchronous Processing

@Async is a Spring annotation that marks a method for asynchronous execution. The method returns immediately while the actual work runs in a separate thread.

Basic Configuration

@SpringBootApplication
@EnableAsync
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

Simple Usage Example

@Service
public class AsyncService {
    @Async
    public void asyncMethod() {
        // This method runs in a separate thread
        System.out.println("Executing async method: " + Thread.currentThread().getName());
    }
}

Combining LambdaQueryWrapper and @Async

Using both together enables efficient asynchronous database operations, especially for complex queries or batch tasks that do not need an immediate response.

Example 1: Asynchronous User List Query

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserMapper userMapper;

    @Async
    public CompletableFuture<List<User>> asyncFindUsers(String name, Integer minAge) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(name), User::getName, name)
                    .ge(minAge != null, User::getAge, minAge);
        List<User> users = userMapper.selectList(queryWrapper);
        return CompletableFuture.completedFuture(users);
    }
}

Example 2: Asynchronous Statistics and Save

@Async
public void asyncStatAndSave(Long departmentId) {
    // Count users in department
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(User::getDepartmentId, departmentId);
    long count = userMapper.selectCount(queryWrapper);

    // Update department statistics
    Department department = new Department();
    department.setId(departmentId);
    department.setUserCount(count);
    departmentMapper.updateById(department);

    // Record statistics log
    StatLog statLog = new StatLog();
    statLog.setDepartmentId(departmentId);
    statLog.setCount(count);
    statLog.setStatTime(LocalDateTime.now());
    statLogMapper.insert(statLog);
}

Advanced Applications and Optimizations

Custom Thread‑Pool Configuration

By default, @Async uses SimpleAsyncTaskExecutor, which is not suitable for production. Define a custom thread pool:

@Configuration
public class AsyncConfig implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("AsyncExecutor-");
        executor.initialize();
        return executor;
    }
}

Exception Handling

Exceptions thrown in asynchronous methods do not propagate to the caller and must be handled explicitly:

@Async
public CompletableFuture<List<User>> asyncFindUsersWithExceptionHandling(String name) {
    try {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(User::getName, name);
        List<User> users = userMapper.selectList(queryWrapper);
        return CompletableFuture.completedFuture(users);
    } catch (Exception e) {
        log.error("Async user query failed", e);
        return CompletableFuture.completedFuture(Collections.emptyList());
    }
}

Transaction Management

Asynchronous methods do not inherit the caller's transaction. Use a new transaction if needed:

@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void asyncUpdateWithTransaction(User user) {
    // This update runs in a new transaction
    userMapper.updateById(user);
}

Practical Use Cases

Backend Report Generation

@Async
public void asyncGenerateUserReport(LocalDate startDate, LocalDate endDate, String reportPath) {
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.between(User::getCreateTime, startDate.atStartOfDay(), endDate.atTime(23, 59, 59))
                .orderByAsc(User::getCreateTime);
    List<User> users = userMapper.selectList(queryWrapper);
    // Generate Excel report
    generateExcelReport(users, reportPath);
    // Send notification
    sendReportReadyNotification(reportPath);
}

Batch Data Processing

@Async
public CompletableFuture<Integer> asyncBatchProcessUsers(List<Long> userIds) {
    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.in(User::getId, userIds);
    List<User> users = userMapper.selectList(queryWrapper);
    int processedCount = 0;
    for (User user : users) {
        if (processUser(user)) {
            processedCount++;
        }
    }
    return CompletableFuture.completedFuture(processedCount);
}

Performance Considerations and Best Practices

Use async only where it truly benefits performance; simple queries may be faster synchronously.

Control concurrency to avoid exhausting database connections.

Leverage MyBatis‑Plus batch operations for bulk updates.

Handle results with CompletableFuture for convenient post‑processing.

Monitor task execution and thread‑pool health.

Conclusion

Combining MyBatis‑Plus LambdaQueryWrapper with Spring Boot @Async provides Java backend developers a powerful toolkit. LambdaQueryWrapper offers type‑safe, elegant query construction, while @Async simplifies asynchronous programming. Proper use of both can markedly improve response speed and processing capacity for complex queries, batch jobs, and background tasks. In real projects, choose the appropriate combination based on scenarios and pay attention to thread‑pool configuration, exception handling, and transaction management to ensure stability and reliability.

image.png
image.png
image.png
image.png
MyBatis-PlusAsynchronous ProgrammingLambdaQueryWrapperSpring @Async
JD Tech Talk
Written by

JD Tech Talk

Official JD Tech public account delivering best practices and technology innovation.

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.