Closed cmdjulian closed 2 months ago
Coroutine repositories are built on top of Reactive repositories. With JPA not supporting a reactive API (note: not talking about Hibernate specifically), we do not have any means to implement reactive JPA in the first place.
Reactive flows do not (really) care about threading, and so does Kotlin Coroutines not care either.
Regarding jooq:
Starting from jOOQ 3.17, the jooq-kotlin-coroutines extension module allows for bridging between the reactive streams API and the coroutine APIs.
So the actual magic is that jooq uses R2DBC drivers that have a different sense of context propagation. Also, jooq ties itself to a connection by encapsulating exactly that one resource.
Spring's Transaction Management requires either imperative (your code stays on the same thread) or reactive (reactive context propagation) code styles but you cannot mix these because of how context is propagated.
Offloading calls to a dedicated executor that maintains transactional scopes, regardless of whether the executor uses Virtual Threads or utilities from Kotlin Coroutines, is likely the only good workaround.
Going forward, we do not plan on enhancing asynchronous JPA capabilities. In some sense, Kotlin created that problem space; It would be great if Kotlin had an answer on how to integrate existing non-Coroutine code in a similar way that Project Loom brought into the JVM instead of requiring all other parts of the ecosystem to follow Kotlin rules.
This is a small extract from my demo project from GitHub including some tests:
I found multiple problems:
fun findAllFlow(): Flow<HelloEntity>
results in startup failure asIllegaleStateException: Reactive Repositories are not supported by JPA
suspend fun suspendFindById(id: Long): HelloEntity?
returningnull
cause the underlying entity is not found results inInvalidDataAccessApiUsageException: Reactive source object must not be null
ConversionFailedException: Failed to convert from type [com.example.demo.HelloEntity] to type [reactor.core.publisher.Flux<?>] for value [com.example.demo.HelloEntity@15045e40]
@Transactional
. When calling the serviceupdate
method, an error is triggered from some of the Spring Data repository infrastructure code:TransactionRequiredException: Executing an update/delete query
. Seems like the transaction is not properly propagatedAt the moment suspend functions are pretty unusable at all from my app. Is this something which should be supported? Would definitely appreciate it. The only thing I found is within the Spring Data Commons Reference Documentation, which seems to outline support.
Some more background information: We use a lot of suspending functions in our Kotlin Spring Boot app which get invoked within the controller. At the moment we use blocking JPA repository methods and wrap them manually in a
withContext(Dispatcher.IO)
call, which is not just tedious, but also error prone.Being able to natively use them within the repository would help us in reducing complexity here.
For instance, JooQ also supports Kotlin Coroutines even for blocking JDBC drivers.
Other considered resources: