TL;DR
-
jOOQ
の管理するConnection
とSpring
のTransactionManager
が管理するConnection
は異なっている- このため、
ConnectionFactory
をDSL.using
に渡してDSLContext
を得る形式では、Transactional
アノテーションが機能しない
- このため、
- 当該コンテキストにおける
Connection
を取得し、そこに対してクエリを実行すれば、Transactional
アノテーションが機能するようになる
やり方
追記: サンプルコードでのコネクションリークについて
以下に示すサンプルコードでは、トランザクション無しで実行するとコネクションリークが発生します。
原因はconnection
がclose
されないことです。
トランザクション有りの場合はTransactionManager
が適切にclose
してくれるため、コネクションリークは発生しません。
自分は以下のように、トランザクション非開始だった場合は最後にコネクションをclose
するようなutil
を作って対応しました。
fun getConnection(): Mono<Connection> = TransactionSynchronizationManager.forCurrentTransaction()
.map { (it.getResource(cfi) as ConnectionHolder).connection }
.onErrorResume(NoTransactionException::class.java) {
cfi.create().toMono().flatMap { connection ->
Mono.just(connection)
.doFinally { connection.close().toMono().subscribe() }
}
}
また、諸事情で自分は使えなかったものの、Mono/Flux.usingWhen
を使う方法も有ります(こちらの方が望ましい気がします)。
H2
に対してロールバックするサンプルプロジェクトは↓のブランチに上げています。
下記はその中での登録コードです。
ConnectionFactoryUtils.getConnection
で当該コンテキストにおけるConnection
を取得して用いるのが重要です。
import io.r2dbc.spi.ConnectionFactory
import kotlinx.coroutines.reactive.awaitSingle
import org.jooq.generated.tables.records.FooTableRecord
import org.jooq.generated.tables.references.FOO_TABLE
import org.jooq.impl.DSL
import org.springframework.r2dbc.connection.ConnectionFactoryUtils
import org.springframework.stereotype.Repository
import reactor.core.publisher.Mono
@Repository
class Repository(private val cfi: ConnectionFactory) {
suspend fun save(value: String): FooTableRecord {
return ConnectionFactoryUtils.getConnection(cfi)
.flatMap {
val ctxt = DSL.using(it)
Mono.from(ctxt.insertInto(FOO_TABLE).values(value).returning())
}.map { it.into(FOO_TABLE) }
.awaitSingle()!!
}
}
文脈に関する補足
R2DBC
接続でjOOQ
を利用する際のサンプルコードは、以下のような形式で、ConnectionFactory
を直接用いるものが見受けられます。
import io.r2dbc.spi.ConnectionFactory
import org.jooq.DSLContext
import org.jooq.impl.DSL
import org.springframework.context.annotation.Bean
@Bean
fun jooqDSLContext(cfi: ConnectionFactory): DSLContext = DSL.using(cfi).dsl()
一方、このやり方ではTransactional
アノテーションが機能しません1。
これは、jOOQ
の管理するConnection
とSpring
のTransactionManager
が管理するConnection
が異なっているためです。
Transactional
アノテーションを機能させるには、必ず当該コンテキストにおけるSpring
管理のConnection
に対して読み書きを行う必要が有ります。
終わりに
@nakamura-to さん、この件の調査にご協力いただきありがとうございました。
おまけ
クエリ毎にDSLContext
を使い捨てにしない方法の考察です。
-
少なくとも自分が検証した限りでは。 ↩