Why @Transactional Fails with @Async in Spring Boot 3 – A Deep Dive
This article explains how @Transactional and @Async normally cooperate in Spring Boot, demonstrates cases where the transaction does not roll back when @EnableAsync order is changed, analyzes the underlying AOP proxy creation and bean post‑processor execution order, and introduces new Spring 6.2 transaction‑rollback configuration options.
1. Introduction
In Spring Boot development, the @Transactional and @Async annotations are frequently used for transaction management and asynchronous processing, respectively.
@Transactional declares that a method should run within a transactional context, guaranteeing ACID properties via AOP proxies (default propagation PROPAGATION_REQUIRED ).
@Async marks a method for asynchronous execution, delegating the call to a thread pool to avoid blocking the main thread.
2. Problem Reproduction
2.1 Code Example
<code>@Transactional
@Async
public void processProduct(Product product) {
this.productRepository.saveAndFlush(product);
this.emailService.send();
}
@Service
public class EmailService {
public void send() {
System.err.printf("%s - 发送邮件", Thread.currentThread().getName());
System.err.println(1 / 0); // deliberate exception
}
}
</code>The send method throws an exception. The question is whether the transaction in processProduct will be rolled back.
2.2 Unit Test
<code>@Resource
private ProductService productService;
@Test
public void testCreateProduct() {
Product product = new Product("Spring全家桶实战案例源码", 70D);
this.productService.processProduct(product);
}
</code>Running the test with the default configuration shows that no data is inserted into the database, confirming that the transaction is rolled back as expected.
2.3 Changing Execution Order
<code>@Configuration
@EnableAsync(order = Ordered.HIGHEST_PRECEDENCE)
public class AsyncConfig {
}
</code>After setting @EnableAsync to the highest precedence and re‑running the test, the database records are persisted, indicating that the transaction did not roll back.
3. Root Cause Analysis
3.1 Proxy Creation Mechanism
When spring-boot-starter-aop is on the classpath, Spring registers AnnotationAwareAspectJAutoProxyCreator via @EnableAspectJAutoProxy . This BeanPostProcessor creates proxies based on Advisors.
High‑level aspects declared with @Aspect are turned into low‑level Advisor objects.
Custom Advisor implementations can also be provided directly.
The proxy is built by combining the BeanPostProcessor with the appropriate Advisor.
3.2 @Transactional Implementation
Including spring-boot-starter-data-jpa or spring-boot-starter-data-jdbc triggers @EnableTransactionManagement , which registers BeanFactoryTransactionAttributeSourceAdvisor and InfrastructureAdvisorAutoProxyCreator . However, AnnotationAwareAspectJAutoProxyCreator has higher priority, so it ultimately creates the transactional proxy.
3.3 @Async Implementation
@EnableAsync registers AsyncAnnotationBeanPostProcessor and its advisor AsyncAnnotationAdvisor . If the target bean is already proxied, the async advisor is simply added to the existing proxy.
3.4 BeanPostProcessor Execution Order
By default, AnnotationAwareAspectJAutoProxyCreator has the highest precedence, so it creates the @Transactional proxy first, followed by the @Async proxy. The async thread runs inside the transaction, allowing rollback on exceptions.
When @EnableAsync(order = Ordered.HIGHEST_PRECEDENCE) is set, the async processor runs before the transactional one. The async proxy is created first; then the transactional processor creates a second proxy that wraps the async proxy. The transaction starts on the main thread, but the async method executes in a separate thread, so an exception in the async task does not trigger a rollback.
4. New Feature in Spring 6.2
Starting with Spring 6.2, @EnableTransactionManagement adds a rollbackOn attribute, allowing global configuration of rollback policies without annotating each @Transactional method.
<code>@Configuration
@EnableTransactionManagement(rollbackOn = RollbackOn.ALL_EXCEPTIONS)
public class TxConfig {
}
public enum RollbackOn {
RUNTIME_EXCEPTIONS,
ALL_EXCEPTIONS
}
</code>This simplifies handling of exception‑driven rollbacks across the application.
5. Conclusion
Under default settings, @Transactional and @Async work together seamlessly. Adjusting the execution order of @EnableAsync can break this coordination, causing transactions not to roll back when async tasks fail. The new rollbackOn attribute in Spring 6.2 provides a more convenient way to control rollback behavior globally.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.