3
0

More than 1 year has passed since last update.

【SpringWebflux】R2DBC接続でTransactionalアノテーションを機能させる【jOOQ】

Last updated at Posted at 2022-08-13

TL;DR

  • jOOQの管理するConnectionSpringTransactionManagerが管理するConnectionは異なっている
    • このため、ConnectionFactoryDSL.usingに渡してDSLContextを得る形式では、Transactionalアノテーションが機能しない
  • 当該コンテキストにおけるConnectionを取得し、そこに対してクエリを実行すれば、Transactionalアノテーションが機能するようになる

やり方

追記: サンプルコードでのコネクションリークについて

以下に示すサンプルコードでは、トランザクション無しで実行するとコネクションリークが発生します。
原因はconnectioncloseされないことです。
トランザクション有りの場合は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の管理するConnectionSpringTransactionManagerが管理するConnectionが異なっているためです。

Transactionalアノテーションを機能させるには、必ず当該コンテキストにおけるSpring管理のConnectionに対して読み書きを行う必要が有ります。

終わりに

@nakamura-to さん、この件の調査にご協力いただきありがとうございました。

おまけ

クエリ毎にDSLContextを使い捨てにしない方法の考察です。

  1. 少なくとも自分が検証した限りでは。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0