I have this service method:
@Transactional
override suspend fun deleteByCarId(carId: Long) {
routeRepository.deleteByCarId(carId)
routePlanRepository.deleteByCarId(carId)
carRepository.deleteById(carId)
}
The route plan (middle) and the the car (last line) are deleted, but the deletes on the routeRepository
are not executed.
interface RouteRepository : CoroutineCrudRepository<Route, Long> {
fun deleteByCarId(carId: Long)
// ...
}
interface RoutePlanRepository : CoroutineCrudRepository<RoutePlan, Long> {
suspend fun deleteByCarId(carId: Long)
// ...
}
So, I figured out that this probably is because the RouteRepository
misses a suspend
on the delete method, but can someone please explain, why this is important?
EDIT 1
By further thinking about the more general case, I think non-suspending repository function that create a Flow
seem to not necessarily be marked as suspend
. But I don't understand why. All other methods seem to need suspend
- but why? Normally I can run a non-suspending function from a coroutine and it is executed (of course we should not because it might block the thread - right?).
My assumption is that creating a Flow
does not need to be suspendable because the action of creating it is fast and only the subscription on it will eventually executed the query.
In the above example there is no subscription to a Flow
because the method returns Unit
- which is why the delete is not executed?
However, I still do not understand, why marking the repository method with suspend
changes the behavior. Now the creation of the query is an async operation in itself and thereby it (the creation of the delete query) becomes part of the overall request handling chain?
But I would expect that creating a Query within a suspending function does not automatically subscribes? Can someone explain?
EDIT 2
I created this issue because I think it should be better documented what is possible and what not: https://github.com/spring-projects/spring-data-commons/issues/2503
The doc currently states that the delete
requires a suspend
but it is not explicitly mentioned:
For return values, the translation from Reactive to Coroutines APIs is the following:
fun handler(): Mono<Void>
becomessuspend fun handler()
fun handler(): Mono<T>
becomessuspend fun handler(): T
orsuspend fun handler(): T?
depending on if the Mono can be empty or not (with the advantage of being more statically typed)
fun handler(): Flux<T>
becomesfun handler(): Flow<T>
https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/#kotlin.coroutines.reactive