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.
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.
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.
JD Tech Talk
Official JD Tech public account delivering best practices and technology innovation.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
