Spring boot r2dbc transactional: which method to annotate
Asked Answered
F

1

7

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?

Furlani answered 20/1, 2021 at 23:32 Comment(1)
Have you seen any issues with the transaction management in case of spring webflux? Does this work without issues just like in spring web?Heartrending
W
5

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(..)

Wilie answered 21/1, 2021 at 12:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.