Jasper Aarts
Jasper Aarts

Reputation: 101

@Async and @Transaction aspect order

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

Answers (2)

Janning Vygen
Janning Vygen

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

nikita21
nikita21

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

Related Questions