I am using spring-boot 2.4.2 with webflux to connect to a postgres database.
I observed a behavior while using @Transactional
that I don't understand.
To showcase the behavior, I created an example application that attempts to add rows to two tables; table "a" and table "b". The insert to table "a" is expected to fail with a duplicate key violation. Given that transactional is used, I expect no rows to be added to table "b".
However, depending which method I annotate with @Transactional
I get different results.
If I annotate the controller method, things work as expected and no row is added to table B.
@PostMapping("/")
@Transactional
public Mono<Void> postEntities() {
return demoService.doSomething();
}
DemoService looks like this:
public Mono<Void> doSomething() {
return internal();
}
public Mono<Void> internal() {
Mono<EntityA> clash = Mono.just(EntityA.builder().name("clash").build()).flatMap(repositoryA::save);
Mono<EntityB> ok = Mono.just(EntityB.builder().name("ok").build()).flatMap(repositoryB::save);
return ok.and(clash);
}
If I move the @Transactional
annotation from the controller to doSomething()
, then the transactions still work as expected.
However, if I move the @Transactional
annotation to internal()
, then transactions don't work as expected. A row is added to table "b".
The full code of this example is here: https://github.com/alampada/pg-spring-r2dbc-transactional
I don't understand why moving the annotation to the internal()
method causes issues to transaction handling. Could you please explain?
From the Spring reference documentation : Using @Transactional
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code (that is, @PostConstruct).
Here the call from doSomething() to internal() is a self-invocation.
Do note that Spring Framework’s declarative transaction support is enabled via AOP proxies.
Spring reference documentation : Understanding AOP Proxies will provide clarity on why self-invocation does not work on proxies. Do read through the section starting with The key thing to understand here is that the client code inside the main(..)