Affects: Spring Boot v2.4.3, Spring v5.3.4


@Transactional annotation rollback transaction instead of commit it.

I have the following method:

@Transactional
public Publisher<?> doSomething() {
      final Mono<?> result = myRepo.getById()
           .flatMap(result -> myRepo.save().thenReturn(result));
      return result;
}

If I call this methods like that:

Mono.from(service.doSomething())

The transaction is rollbacked instead of committed.

If think that is due to this PR https://github.com/spring-projects/spring-framework/pull/25256/files

what I understand, I that: 1. the transaction annotation transform my Mono to a Flux 2. the Mono.from(service.doSomething()).then() transform the Flux to a Mono 3. So When the flux return it's first value the Mono.from cancel the transaction Flux and so it's rollbacked

I have a work around by removing the Mono.from, but I'm afraid of other possible issues

Comment From: sbrannen

@mp911de, thoughts?

Comment From: mp911de

There are a couple of things going on here.

  1. The method is defined as Publisher. Spring's TransactionInterceptor applies a reactive transaction and uses the Flux adapter lacking further type information. Calling doSomething(…) therefore effectively returns Flux instance as methods declaring a plain Publisher return type default to Flux handling.
  2. Mono.from(…) not only adopts a Publisher into Mono but also enforces the Mono contract of completing downstream once the upstream Publisher emits an element. In consequence, the upstream sees a cancel signal which rolls back the transaction (as per the referenced pull request).

You can do two things to eliminate the cancellation:

  1. Declare your service method returning Mono so Spring's transaction infrastructure retains the Mono type. As consequence, you're not required to consume the method result via Mono.from(…)
  2. Use Mono.fromDirect(…). This method assumes that the given publisher emits at most one data element and sticks to the Mono contract. Using this method bears some risk if the transactional method implementation changes without changing the callers.

Comment From: deblockt

Thanks for the response, It's work fine with a Mono.fromDirect or using Mono as return type.

I have not found documentation about this behavior. Is it going to stay like this ? Or a fix is possible?

Comment From: mp911de

There's a bit of documentation in the Spring Framework reference docs on cancel signals. Mono.from(…) is documented with:

     * Expose the specified {@link Publisher} with the {@link Mono} API, and ensure it will emit 0 or 1 item.
     * The source emitter will be cancelled on the first `onNext`.

I agree that it's not obvious from the method name that the upstream gets canceled, however it's a core principle of Mono.