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.
- The method is defined as
Publisher. Spring'sTransactionInterceptorapplies a reactive transaction and uses theFluxadapter lacking further type information. CallingdoSomething(…)therefore effectively returnsFluxinstance as methods declaring a plainPublisherreturn type default toFluxhandling. Mono.from(…)not only adopts aPublisherintoMonobut also enforces theMonocontract of completing downstream once the upstreamPublisheremits an element. In consequence, the upstream sees acancelsignal which rolls back the transaction (as per the referenced pull request).
You can do two things to eliminate the cancellation:
- Declare your service method returning
Monoso Spring's transaction infrastructure retains theMonotype. As consequence, you're not required to consume the method result viaMono.from(…) - Use
Mono.fromDirect(…). This method assumes that the given publisher emits at most one data element and sticks to theMonocontract. 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.