2

I decide to use AspectJ to avoid the fact that a method annotated with @Transactional can't be invoked from the same class

So I add this configuration :

@Configuration
@EnableTransactionManagement(mode= AdviceMode.ASPECTJ)
@EnableLoadTimeWeaving(aspectjWeaving= AspectJWeaving.ENABLED)
public class App implements LoadTimeWeavingConfigurer {

    @Override
    public LoadTimeWeaver getLoadTimeWeaver() {
        return new InstrumentationLoadTimeWeaver();
    }

}

In build.gradle

runtimeOnly("org.aspectj:aspectjweaver:1.9.7")

And I run the app (spring boot app with tomcat embedded) with -javaagent:C:\xx\xx\.m2\repository\org\springframework\spring-instrument-5.3.12.jar

But when I try

public void m1() {
    this.m2()
}


@Transactional(propagation = Propagation.REQUIRED)
public void m2() {
    ....
}

It seems that m2() method is not executed within an transaction,
In the logs when I debug with these logging level :

logging.level.org.springframework.transaction.interceptor=trace
logging.level.org.springframework.orm.jpa=trace

there is no line such :

Creating new transaction with name [xxx.m2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT

Am I missing something there ?

Hayi
  • 6,972
  • 26
  • 80
  • 139
  • Is it executed when called by another Spring component? If so, something is wrong with your configuration and Spring is actually applying its own Spring AOP rather than native AspectJ. Either way, an [MCVE](https://stackoverflow.com/help/mcve) on GitHub would help me to reproduce and solve your problem. – kriegaex Nov 01 '21 at 08:05
  • @kriegaex yes it is executed when I call another bean's `@Transactional` method in my work project, but when I reproduce the the problem on https://github.com/soyousseflechqar/aspectj no transaction is created – Hayi Nov 01 '21 at 15:41
  • I am not a Spring user, just an AOP expert. But your example project does not log anything when calling a `@Transactional` method, neither when calling it from outside the Spring bean or from inside. So how can I verify your claim that it does not work by e.g. changing `fooService.m1();` to `fooService.m2();`? In both cases there is no transaction logging. Maybe you should prepare your MCVE to actually reproduce the problem first. – kriegaex Nov 01 '21 at 17:30
  • @kriegaex I just forgot to add logging levels but now added 2 branches: one with proxy mode that works and the other with aspectj mode that don't. – Hayi Nov 01 '21 at 18:56

2 Answers2

2

I cloned your GitHub project, then added

implementation 'org.springframework:spring-instrument'

You should also limit the Spring (transaction) aspect scope to only weave your own application classes in order to avoid lots of [Xlint:cantFindType] messages when trying to weave Spring's own classes. You can do this by providing your own src/main/resources/org/aspectj/aop.xml file as follows:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>

  <!-- You can also add -Xlint:ignore in order to avoid lots of '[Xlint:cantFindType]' warnings -->
  <weaver options="-verbose -showWeaveInfo">
    <!-- Only weave classes in our application-specific packages -->
    <include within="com.example.aspectj..*"/>
  </weaver>

</aspectj>

The weaver options also make it easier to see which aspects are woven into which joinpoints, e.g. during start-up you see

[AppClassLoader@77556fd] weaveinfo Join point 'method-execution(void com.example.aspectj.services.FooService.m2())' in Type 'com.example.aspectj.services.FooService' (FooService.java:27) advised by around advice from 'org.springframework.transaction.aspectj.JtaAnnotationTransactionAspect' (AbstractTransactionAspect.aj:67)

which proves that FooService.m2() is actually being woven.

For a less "noisy" AspectJ weaver, simply use <weaver options="-showWeaveInfo -Xlint:ignore"> - no more warnings or info about which aspects are registered, but still the information about woven joinpoints, which is important to see, IMO.

Then I started the application with both the Spring instrumentation and AspectJ weaver agents. Only the former was not enough to kick off instrumentation, I needed both agents. Because on JDK 16+ you need to open the java.lang package to the unnamed module in order to be able to apply LTW and I was testing on a recent JDK, I also added the corresponding --add-opens option (not necessary until JDK 15):

--add-opens java.base/java.lang=ALL-UNNAMED
-javaagent:.../aspectjweaver-1.9.7.jar
-javaagent:.../spring-instrument-5.3.12.jar

Then everything works es expected:

o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
com.example.aspectj.AspectjApplication   : Started AspectjApplication in 7.339 seconds (JVM running for 10.046)
com.example.aspectj.AspectjApplication   : running ..
com.example.aspectj.services.FooService  : m1 : called
o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [com.example.aspectj.services.BooService.m3]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(1516190088<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@543f6ccb]
com.example.aspectj.services.BooService  : m3 : called
o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(1516190088<open>)]
o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1516190088<open>)] after transaction
o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [com.example.aspectj.services.FooService.m2]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(45178615<open>)] for JPA transaction
o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@20e4fce0]
com.example.aspectj.services.FooService  : m2 : called
o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(45178615<open>)]
o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(45178615<open>)] after transaction

Update: I am really sorry for problems that come with Spring (Boot) out of the box, but as it is now, you either have to live with the AspectJ core dump files ajdump.*.txt - the transaction aspect weaving still works, like I said before - or use your own aop.xml file (see above). As an alternative to including your own application base package for aspect weaving, you can also go the opposite direction of excluding the classes or packages causing the core dumps. In Spring Boot 2.5.6, you simply add

<exclude within="org.springframework.boot.jdbc.DataSourceBuilder.OraclePoolDataSourceProperties"/>

In Spring Boot 2.3.3, AspectJ complains about this class:

<exclude within="org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer"/>

I think this needs to be fixed in Spring Boot or maybe in Spring Core, Spring-TX or Spring-Aspects, wherever those aspects are located.


Update 2: I have created Spring Core issue #27650 in order to track the AspectJ core dump problem. It is not the root cause of your original problem, because transaction aspect weaving works anyway, but it needs to be addressed in Spring (and possibly in AspectJ) anyway.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • With JDK 11 it still not working after adding AspectJ weaver agent – Hayi Nov 02 '21 at 18:25
  • transaction is created just for `m3` (the one located on other bean), not for `m2` – Hayi Nov 02 '21 at 18:37
  • and i'm noticing a lot of logs like `[AppClassLoader@77556fd] error can't determine implemented interfaces of missing type com.google.gson.Gson when weaving type org.springframework.http.converter.json.GsonHttpMessageConverter when weaving classes when weaving [Xlint:cantFindType]` for different classes – Hayi Nov 02 '21 at 18:38
  • I quickly tested with JDK 11, also no problem. I also see lots of `[Xlint:cantFindType]` messages, but I guess they can be ignored if your are not using the corresponding aspects. They do not seem to impede the transaction management, at least not on my workstation. – kriegaex Nov 02 '21 at 21:34
  • Please note my updated answer. The `com.oracle.database.jdbc:ojdbc8` is gone. Instead, I did the right (clean) thing, added an `aop.xml` file and limited the aspect scope. See if that helps you avoid the warnings. – kriegaex Nov 03 '21 at 05:18
  • But i can't track all files that are using @Transactional and add them to aop.xml, – Hayi Nov 05 '21 at 00:01
  • I think there is a better solution with spring, i'v seen some questions on SO that didn't use your approach and it has worked for them – Hayi Nov 05 '21 at 00:02
  • What are you talking about? You just add your application base package, see above. Did you even try my approach? If something is wrong with it, please explain what. Besides, you asked about self-invocation support with declarative transactions. You cannot achieve that with Spring AOP. If you claim otherwise, please don't just say "I've seen some questions" but link to them or write your own answer, comprehensively describing how you solved this exact problem in another way. Anyway, my answer is correct. I would appreciate you to accept + upvote it. – kriegaex Nov 05 '21 at 07:23
  • **Update:** I added a paragraph describing how to exclude problematic Spring classes rather than including your own application classes in order to avoid the `ajcore.*.txt` core dump files. Maybe you like this approach better, up to you. BTW, the core dump problem occurs in a Maven project just as it does in your Gradle project and across Spring Boot versions, so it is a Spring problem. – kriegaex Nov 05 '21 at 11:26
  • **Update 2:** I have created [Spring Core issue #27650](https://github.com/spring-projects/spring-framework/issues/27650) in order to track the AspectJ core dump problem. It is not the root cause of your original problem, because transaction aspect weaving works anyway, but it needs to be addressed in Spring (and possibly in AspectJ) anyway. – kriegaex Nov 07 '21 at 09:58
  • thanks man for the help i got u now, at first i misunderstood your solution, sorry for the late response I just had a personal stuff so I couldn't test your solution – Hayi Nov 08 '21 at 22:01
  • I notice that it work even if I omit the `@EnableLoadTimeWeaving(aspectjWeaving= AspectJWeaving.ENABLED)` annotation, so LTW is picked by default ? – Hayi Nov 08 '21 at 22:02
  • Spring will notice automatically if the `aspectjweaver` agent is active and deactivate Spring AOP in favour of native AspectJ. – kriegaex Nov 08 '21 at 22:09
  • Spring will notice automatically if the `aspectjweaver` agent is active and deactivate Spring AOP in favour of native AspectJ. – kriegaex Nov 08 '21 at 22:09
0

My understanding is that if you have a bean fooService, and you invoke a method m1 on fooService, and that does not create a transaction so no transaction will be created when you call another method, in this case m2 and this is intended behavior.

If you did it the other way around this would create a transaction as in: m2 calling m1. I only know this because we've had the same problem in our project, which was very confusing. Not sure why it's like this, I tried to look for the documentation on this, if I find it I'll add it to my answer. In summary, a transaction can only be created by calling an @Transactional method on a bean, outside of that bean, but not by the bean itself by another method which is not transactional.

Derrops
  • 7,651
  • 5
  • 30
  • 60
  • I did find this post https://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo – Derrops Nov 08 '21 at 02:45