Reputation: 101
Using Spring Boot 2.1.1.RELEASE / Spring Framework 5.1.4, I have an application with @Async
and @Transactional
annotations enabled through:
@EnableAsync(mode = AdviceMode.ASPECTJ)
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
When running a method that is annotated with both, first the transaction is created and then the asynchronous execution starts. So, the actual method body is not executed inside the transaction.
@Transactional
@Async
public void myAsyncMethod() {
// asynchronous database stuff
}
How can I configure spring / the aspects to actually execute in an order that makes sense, e.g. start the transaction on the new thread?
On a side note, with the older Spring Boot 1.5.17 / Spring Framework 4.3.20 it actually worked.
Demo: https://github.com/jaarts/spring-asynctransaction-demo
Upvotes: 2
Views: 1762
Reputation: 9222
If you are using aspectj with compile time weaving (ctw) with older versions of spring the only solution was to add an Aspect and use DeclarePrecedence like this:
package org.example.config;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclarePrecedence;
@Aspect
@DeclarePrecedence(
"org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect"
+ "," +
"org.springframework.transaction.aspectj.AnnotationTransactionAspect"
)
public class AspectOrdering {}
With newer versions of spring (at least with v6+) this is not needed anymore. Test:
public class AspectOrderingTest {
@Test
public void verifyExecutionOrdering() throws ExecutionException, InterruptedException {
verifyExecutionOrder().get();
}
@Async
@Transactional
public Future<Boolean> verifyExecutionOrder() {
var stackTrace = Thread.currentThread().getStackTrace();
List<String> executionOrder = new ArrayList<>();
for (var stackTraceElement : stackTrace) {
var className = stackTraceElement.getClassName();
if (className.equals("org.springframework.transaction.aspectj.AbstractTransactionAspect")) {
if (!executionOrder.contains("Transaction")) {
executionOrder.add("Transaction");
}
}
if (className.equals("org.springframework.scheduling.aspectj.AbstractAsyncExecutionAspect")) {
if (!executionOrder.contains("Async")) {
executionOrder.add("Async");
}
}
}
// first in Stacktrace, late in executiuon
Collections.reverse(executionOrder);
assertEquals("Async", executionOrder.get(0));
assertEquals("Transaction", executionOrder.get(1));
return CompletableFuture.completedFuture(true);
}
}
Upvotes: 0
Reputation: 49
In Spring 5 Async advice is always execited first. See AsyncAnnotationBeanPostProcessor
public AsyncAnnotationBeanPostProcessor() {
setBeforeExistingAdvisors(true);
}
After that on superclass in postProcessAfterInitialization when advisors aplies code executes
if (this.beforeExistingAdvisors) {
advised.addAdvisor(0, this.advisor);
}
On @EnableTransactionManagement#order javadoc says
Indicate the ordering of the execution of the transaction advisor
but on @EnableAsync
Indicate the order in which the AsyncAnnotationBeanPostProcessor should be applied.
Upvotes: 3