spring-projects / spring-framework

Spring Framework
https://spring.io/projects/spring-framework
Apache License 2.0
55.61k stars 37.75k forks source link

No well-defined advice order for @Transactional and @Async in AspectJ mode #24309

Open kklepacz opened 4 years ago

kklepacz commented 4 years ago

Affects: 5.x.x


Since Spring 5 (spring-aspects 5.x.x), it looks like @Transactional is ignored on the method with @Async when using AdviceMode.ASPECTJ for both @EnableTransactionManagement and @EnableAsync.

Please take a look on following demo: https://github.com/kklepacz/spring-async-transaction-demo

After running mvn clean test you should see following output:

2020-01-07 12:02:50.729  INFO 29454 --- [ async-thread-1] c.e.asynctransactiondemo.AsyncListener   : Transaction in async listener? false

So even if @Transactional annotation is present on handle method, transaction is not active.

Now, let's change Spring version. Update pom to use Spring 4 + Spring Boot 1.5.x

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.22.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

Run mvn clean test again. Output:

2020-01-07 12:06:38.082 DEBUG 29675 --- [ async-thread-1] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [com.example.asynctransactiondemo.AsyncListener.handle]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2020-01-07 12:06:38.082 DEBUG 29675 --- [ async-thread-1] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@3bcd99f8] for JPA transaction
2020-01-07 12:06:38.083 DEBUG 29675 --- [ async-thread-1] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC transaction [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@350a70f1]
2020-01-07 12:06:38.083  INFO 29675 --- [ async-thread-1] c.e.asynctransactiondemo.AsyncListener   : Transaction in async listener? true
2020-01-07 12:06:38.085 DEBUG 29675 --- [ async-thread-1] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2020-01-07 12:06:38.085 DEBUG 29675 --- [ async-thread-1] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@3bcd99f8]
2020-01-07 12:06:38.085 DEBUG 29675 --- [ async-thread-1] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@3bcd99f8] after transaction

Transaction is active.

Is it a bug or a feature and I need some extra configuration to have my Spring Boot 2 version working as expected?

Is it related to AJC compilation anyhow? I can see a difference between an order of compilation in maven output. For Spring Aspects 5.x:

[INFO] Showing AJC message detail for messages of types: [error, warning, fail]
[INFO] Join point 'method-execution(void com.example.asynctransactiondemo.AsyncListener.handle(com.example.asynctransactiondemo.SomeEntitySaved))' in Type 'com.example.asynctransactiondemo.AsyncListener' (AsyncListener.java:19) advised by around advice from 'org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect' (spring-aspects-5.1.9.RELEASE.jar!AbstractAsyncExecutionAspect.class:65(from AbstractAsyncExecutionAspect.aj))
[INFO] Join point 'method-execution(void com.example.asynctransactiondemo.AsyncListener.handle(com.example.asynctransactiondemo.SomeEntitySaved))' in Type 'com.example.asynctransactiondemo.AsyncListener' (AsyncListener.java:19) advised by around advice from 'org.springframework.transaction.aspectj.AnnotationTransactionAspect' (spring-aspects-5.1.9.RELEASE.jar!AbstractTransactionAspect.class:66(from AbstractTransactionAspect.aj))

For Spring Aspects 4.x - order is different:

[INFO] Showing AJC message detail for messages of types: [error, warning, fail]
[INFO] Join point 'method-execution(void com.example.asynctransactiondemo.AsyncListener.handle(com.example.asynctransactiondemo.SomeEntitySaved))' in Type 'com.example.asynctransactiondemo.AsyncListener' (AsyncListener.java:19) advised by around advice from 'org.springframework.transaction.aspectj.AnnotationTransactionAspect' (spring-aspects-4.3.25.RELEASE.jar!AbstractTransactionAspect.class:66(from AbstractTransactionAspect.aj))
[INFO] Join point 'method-execution(void com.example.asynctransactiondemo.AsyncListener.handle(com.example.asynctransactiondemo.SomeEntitySaved))' in Type 'com.example.asynctransactiondemo.AsyncListener' (AsyncListener.java:19) advised by around advice from 'org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect' (spring-aspects-4.3.25.RELEASE.jar!AbstractAsyncExecutionAspect.class:65(from AbstractAsyncExecutionAspect.aj))
Farteen commented 4 years ago

According to the configuration of your demo project, I think you are using Compile-Time Weaving instead of LTW. But Spring project's aspects do not have advice ordering guaranteed( no precedence declaration in .aj file or precedence attribute in aop.xml).