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'sTransactionInterceptor
applies a reactive transaction and uses theFlux
adapter lacking further type information. CallingdoSomething(…)
therefore effectively returnsFlux
instance as methods declaring a plainPublisher
return type default toFlux
handling. Mono.from(…)
not only adopts aPublisher
intoMono
but also enforces theMono
contract of completing downstream once the upstreamPublisher
emits an element. In consequence, the upstream sees acancel
signal 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
Mono
so Spring's transaction infrastructure retains theMono
type. 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 theMono
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
.