【Spring WebFlux】Cannot determine database's type as ConnectionFactory is not options-capable.への対処【R2DBC】

H2データベースを使ってR2DBCpostAllocate/preReleaseの挙動を確認するためのSpring WebFluxプロジェクトを作成していた所、Cannot determine database's type as ConnectionFactory is not options-capable. To be options-capable, a ConnectionFactory should be created with org.springframework.boot.r2dbc.ConnectionFactoryBuilderというエラーが出て詰まったので、対策用の備忘録です。
あまりよく確認していませんが、OptionsCapableConnectionFactoryを使うと上手くいくようでした。

import io.r2dbc.pool.ConnectionPool
import io.r2dbc.pool.ConnectionPoolConfiguration
import io.r2dbc.spi.ConnectionFactories
import io.r2dbc.spi.ConnectionFactory
import io.r2dbc.spi.ConnectionFactoryOptions
import org.springframework.boot.r2dbc.OptionsCapableConnectionFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration

@Configuration
class R2dbcConfiguration : AbstractR2dbcConfiguration() {
    @Bean
    override fun connectionFactory(): ConnectionFactory {
        val h2UrlStr = "r2dbc:h2:mem:///testdb"

        val defaultConnectionFactory = ConnectionFactories.get(h2UrlStr)
        val config = ConnectionPoolConfiguration
            .builder(defaultConnectionFactory)
            .postAllocate { _ -> TODO() }
            .build()

        return OptionsCapableConnectionFactory(
            ConnectionFactoryOptions.parse(h2UrlStr),
            ConnectionPool(config)
        )
    }
}

PostgreSQLでやっている時にはConnectionPool(config)で上手くいくようだったため、何でH2の時だけこれが出たのかはよく分かっていません。

【Windows】IMEの入力言語が勝手に切り替わる問題への対処2種【PowerToys】

TL;DR

  • Windowsでは、特にゲーム中のキーボード操作によって入力言語が切り替わり、日本語入力できなくなる問題が有る
    • IMEが英語モードになってしまう
  • 入力言語切り替えのショートカットはデフォルトで2種類有り、それぞれを無効化することで切り替わりが発生しなくなる
    • 左Alt + Shift
    • Windows + Space

左Alt + Shiftへの対処

↓のサイトを参考に、キーボードの詳細設定 -> 入力言語のホットキー -> キーの詳細設定 -> キー シーケンスの変更 -> 入力言語の切り替え -> 割り当てなしに設定して無効化しました。

www.teradas.net

設定後の画像は以下の通りです。

Windows + Spaceへの対処

こちらは単純な設定では対処できませんが、Microsoftが公式に提供しているソフト(Microsoft PowerToys)を用いれば容易に無効化できます。
入手方法は幾つか存在しますが、Microsoft Store経由でインストールするのが簡単だと思います。

apps.microsoft.com

インストールした上で、Keyboard Manager -> ショートカットの再マップ -> 物理ショートカット: Windows + Space / マップ先: (任意のキー)と設定することで、入力言語切り替えを無効化出来ます。
画像ではWindows + Spaceの入力をSpaceの単体入力に変換しています。

マウスの検索の無効化について

Microsoft PowerToysでは、デフォルトでマウスの検索機能が有効になっているようでした。
これが有効だと、Ctrlを2回押した際に、全体が暗くなった上でマウスの周りだけ明るい表示になります。

当然ゲーム中に起きると非常に邪魔であるため、この機能が必要無い方は無効化することをお勧めします。
簡単なやり方は、マウス ユーティリティからマウスの検索を無効にするだけです。

後書き

Apex Legendsをプレイしている最中にこれが頻発して嫌になったので書きました。
何で同じ機能に複数ショートカットが有る上に、かつ片方は簡単に無効化できないようになってるんだろう。

【jOOQ】R2DBC接続でDSLContextを上手く管理する方法を考える【Spring】

以下の記事を書いていて、「DSLContextを使い捨てたら非効率なのでは?」と感じたので、管理方法を考えてみたメモです。
その他の便利要件も含めています。

あくまでメモなので、これがどれ位性能に影響するか等は未検証です。

qiita.com


前提

ソースコード

管理用クラスは以下の通りです。

import io.r2dbc.spi.ConnectionFactory
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.reactive.asFlow
import kotlinx.coroutines.reactive.awaitSingle
import org.jooq.DSLContext
import org.jooq.Record
import org.jooq.ResultQuery
import org.jooq.SQLDialect
import org.jooq.impl.DSL
import org.springframework.r2dbc.connection.ConnectionHolder
import org.springframework.stereotype.Component
import org.springframework.transaction.NoTransactionException
import org.springframework.transaction.reactive.TransactionSynchronizationManager
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.toMono

@Component
class JooqContext(private val cfi: ConnectionFactory) {
    companion object {
        // TransactionSynchronizationManagerのリソースにDSLContextをバインドする際に用いるキー
        private const val DSL_CONTEXT_KEY = "DSLContext"
    }

    // 読み込み時にトランザクションが開始されていなければ使うDSLコンテキスト
    private val defaultContextForRead = DSL.using(cfi, SQLDialect.H2).dsl()

    // DSLContextは一々インスタンスを使い捨てるとオーバーヘッドが大きそうなため、コンテキストにバインドしてそれを使い回す
    private fun getDslContext(manager: TransactionSynchronizationManager): DSLContext {
        manager.getResource(DSL_CONTEXT_KEY)?.apply { return (this@apply as DSLContext) }

        val connection = (manager.getResource(cfi) as ConnectionHolder).connection
        val dslContext = DSL.using(connection, SQLDialect.H2).dsl()

        manager.bindResource(DSL_CONTEXT_KEY, dslContext)

        return dslContext
    }

    // テストでモックするため公開
    fun getCurrentDsl(): Mono<DSLContext> = TransactionSynchronizationManager
        .forCurrentTransaction() // forCurrentTransactionはトランザクション非開始でエラーが生じる
        .map { getDslContext(it) }

    // readは基本的に1件以上の取得が期待されるため、fluxを返す
    fun <R : Record> read(query: DSLContext.() -> ResultQuery<R>): Flow<R> = getCurrentDsl()
        // read時はトランザクション非開始でデフォルトにフォールバック
        .onErrorReturn(NoTransactionException::class.java, defaultContextForRead)
        .flatMapMany { query(it) }
        .asFlow()

    // write時はトランザクションを強制するため、トランザクション非開始エラーを処理しない
    suspend fun <R : Record> write(query: DSLContext.() -> ResultQuery<R>): R = getCurrentDsl()
        .flatMap { query(it).toMono() }
        .awaitSingle()

    fun <R : Record> writeMany(query: DSLContext.() -> ResultQuery<R>): Flow<R> = getCurrentDsl()
        .flatMapMany { query(it) }
        .asFlow()
}

利用側は以下のようになります。
DSLContextMono/Fluxの生成処理が管理クラス側に隠ぺいできた分だけ利用側がすっきりしています。

import org.jooq.generated.tables.records.FooTableRecord
import org.jooq.generated.tables.references.FOO_TABLE
import org.springframework.stereotype.Repository

@Repository
class Repository(private val ctxt: JooqContext) {
    suspend fun save(value: String): FooTableRecord {
        return ctxt.write { insertInto(FOO_TABLE).values(value).returning() }
            .map { it.into(FOO_TABLE) }
    }
}

解説

追記: ConnectionFactoryUtils.getConnectionを用いる際のコネクションリークについて

以下の解説ではConnectionFactoryUtils.getConnectionの利用について触れていますが、これを使う際には適切にcloseしなければコネクションリークが発生する点にご注意下さい。
詳しくは以下にまとめています。

qiita.com


コネクションやDSLContextの管理はTransactionSynchronizationManagerを直接利用するようにしています。
この形にしている理由は以下の通りです。

  • トランザクション無しで書き込もうとしたらエラーにしたい」を実現するのに都合がいい
  • TransactionSynchronizationManagerにリソースをバインドすることで、DSLContextを使い回せる

特にコネクション取得について、Qiitaの方ではConnectionFactoryUtils.getConnectionを用いていましたが、例外もしっかりケアされていたため、安全性だけ考えるとこちらの方が良いと思います。
この記事で紹介したコードはあくまで自分のユースケースでちゃんと動きそうという程度しか見ていません。 docs.spring.io

【Spring】JDBC接続で、suspend関数に対してTransactionalアノテーションを利用してのロールバックが効くか確認する【Kotlin】

お詫び

過去公開していた記事では、Spring WebFluxで複数リクエストを同時に処理する場合を考慮していませんでした。
JDBC接続 x Spring WebFluxトランザクション管理をすると壊れる可能性が有るため、やらないことをお勧めします。

R2DBC接続でトランザクションが効かない!」という方は下記の記事が参考になるかもしれません。

qiita.com

何故壊れるのか

トランザクション管理にThreadLocalが利用されているためです。

github.com

Spring WebMvcでは、リクエスト毎にスレッドが生成されて利用されるため、ThreadLocalを使っても1リクエスト1スレッドで処理できます。
一方、Spring WebFluxでは、固定数のスレッドで全てのリクエストを処理します。
つまり、別リクエストの影響を受けて壊れる可能性が有るということです。

ThreadLocalの利用に関してはProject ReactorSpring WebFluxの基盤)のサイトでも言及があります。

projectreactor.io


TL;DR

  • 登録関数とそれを呼び出す関数がそれぞれsuspend関数か否かに関わらず、ロールバックは成功するようだった
    • H2Postgres両方でこの結果となった
  • ただし、呼び出し関数がsuspend関数で、coroutineScopeを切って登録関数を呼び出した際に不可解な挙動が生じたため、注意が必要

文脈

jOOQ/R2DBCロールバックできることが確認できたので、この文脈は読み飛ばして下さい。

qiita.com

自分が調査した限り、Spring 2.7.2で、jOOQ 3.16.4R2DBC接続で利用する場合、Transactionalアノテーションを利用してのロールバックは効きません。 この問題は以前のバージョンにも存在しています。

原因に関して調査したものの、明確な解決策を見つけることはできませんでした。 そもそも原因がjOOQなのかすら分かっていませんが、一応jOOQ 3.18に向けて幾つかの作業は進行中のようです。

github.com

jOOQは使いたい、でもTransacionalを使うならR2DBC接続にはできない……ということで、JDBC接続のjOOQsuspend関数から使った場合にどこまでトランザクションが効くか確認します。 この検証についてjOOQは多分あまり関係ないですが、一応自分の用途に合わせてjOOQを嚙ませています。

検証内容

検証に用いたプロジェクトは下記リポジトリに置いてあります。

github.com

検証内容としては、登録関数とそれを呼び出す関数がそれぞれsuspend関数か否かでTransactionalの挙動がどう変化するかを確認します。
確認方法は以下の通りです。

  1. テーブルに適当な値を登録し、登録が成功したことを確認する
  2. RuntimeExceptionthrowする(比較用にthrowしないパターンも用意する)
  3. レコードを取得してみて、ロールバックされているかを確認する

まず、登録関数と呼び出し関数のコードはそれぞれ以下の通りです。

// 登録関数
import org.jooq.DSLContext
import org.jooq.generated.tables.references.FOO_TABLE
import org.springframework.stereotype.Repository

@Repository
class Repository(private val create: DSLContext) {
    fun findAll() = create.selectFrom(FOO_TABLE).fetch()

    fun nonSuspendSave(value: String) = create.insertInto(FOO_TABLE).values(value).returning().fetchAnyInto(FOO_TABLE)

    suspend fun suspendSave(value: String) =
        create.insertInto(FOO_TABLE).values(value).returning().fetchAnyInto(FOO_TABLE)
}
// 呼び出し関数
import kotlinx.coroutines.runBlocking
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

object TestEx : RuntimeException("fail for test")

@Suppress("FunctionName")
@Component
@Transactional
class Caller(private val repository: Repository) {
    private fun getFuncName() = Throwable().stackTrace.let { it[1].methodName }

    // region 呼び出し元が非suspend
    fun nonSuspend_nonSuspend() {
        val funcName = getFuncName()
        println("$funcName on save\n${repository.nonSuspendSave(funcName)}")
    }

    fun nonSuspend_nonSuspend_fail() {
        val funcName = getFuncName()
        println("$funcName on save\n${repository.nonSuspendSave(funcName)}")
        throw TestEx
    }

    fun nonSuspend_suspend() {
        val funcName = getFuncName()
        println("$funcName on save\n${runBlocking { repository.suspendSave(funcName) }}")
    }

    fun nonSuspend_suspend_fail() {
        val funcName = getFuncName()
        println("$funcName on save\n${runBlocking { repository.suspendSave(funcName) }}")
        throw TestEx
    }
    // endregion

    // region 呼び出し元がsuspend
    suspend fun suspend_nonSuspend() {
        val funcName = getFuncName()
        println("$funcName on save\n${repository.nonSuspendSave(funcName)}")
    }

    suspend fun suspend_nonSuspend_fail() {
        val funcName = getFuncName()
        println("$funcName on save\n${repository.nonSuspendSave(funcName)}")
        throw TestEx
    }

    suspend fun suspend_suspend() {
        val funcName = getFuncName()
        println("$funcName on save\n${repository.suspendSave(funcName)}")
    }

    suspend fun suspend_suspend_fail() {
        val funcName = getFuncName()
        println("$funcName on save\n${repository.suspendSave(funcName)}")
        throw TestEx
    }
    // endregion
}

データベースはH2を使っています。

検証結果

以下のテストから呼び出して確認を行いました。

import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest

@SpringBootTest
private class CallerTest @Autowired constructor(
    val caller: Caller,
    val repository: Repository
) {
    private fun <T> exec(thrower: () -> T?): Any? = try { thrower() } catch (e: TestEx) { e }

    @BeforeEach
    fun callSaves() {
        exec { caller.nonSuspend_nonSuspend() }
        exec { caller.nonSuspend_nonSuspend_fail() }
        exec { caller.nonSuspend_suspend() }
        exec { caller.nonSuspend_suspend_fail() }

        exec { runBlocking { caller.suspend_nonSuspend() } }
        exec { runBlocking { caller.suspend_nonSuspend_fail() } }
        exec { runBlocking { caller.suspend_suspend() } }
        exec { runBlocking { caller.suspend_suspend_fail() } }
    }

    @Test
    fun print() {
        println("result:\n${repository.findAll()}")
    }
}

結果は以下のようになりました。
fail(= throwする)パターンは全てロールバックされていることが分かります。

result:
+------------------------------+
|TEXT_VALUE                    |
+------------------------------+
|nonSuspend_nonSuspend         |
|nonSuspend_suspend            |
|suspend_nonSuspend$suspendImpl|
|suspend_suspend$suspendImpl   |
+------------------------------+

懸念点が無いでもありませんが、一応普通にやっている間はロールバックされること確認できました。
また、コードは載せられませんが、PostgreSQLでやった場合にも同様の結果となりました。

補足: 呼び出し関数がsuspend関数で、coroutineScopeを切って登録関数を呼び出した際の不可解な挙動について

登録関数を以下のように変更した所、2件登録したレコードがどちらもロールバックされないという結果になりました。

suspend fun suspend_suspend_fail() {
    val funcName = getFuncName()
    println("$funcName on save\n${repository.suspendSave(funcName)}")
    coroutineScope {
        launch { println("$funcName on save2\n${repository.suspendSave(funcName + "2")}") }
    }.join()

    throw TestEx
}

coroutineScoperunBlockingで動かすなどすれば2件ともロールバックされているようでした(= 呼び出し関数が非suspend関数の場合にはrunBlockingせざるを得ないため、この現象は発生しない)。

coroutineScopeの内側はともかく、外側までロールバックされなくなるというのは少し直感に反する挙動に感じましたが、coroutineScopeを切って操作するというのがトランザクション処理と相性が悪そうなのはそうなので、やらないよう気を付ける必要が有るかなと思っています。

【jOOQ】etiennestuder/gradle-jooq-plugin(nu.studer.jooq)でコード生成時にjava.lang.ClassNotFoundException: jakarta.xml.bind.annotation.XmlSchemaが出る状況への対処

やり方

dependenciesjooqGenerator("jakarta.xml.bind:jakarta.xml.bind-api:3.0.0")を追加すれば生成が通りました。

dependencies {
    /* 略 */
    jooqGenerator("jakarta.xml.bind:jakarta.xml.bind-api:3.0.0")
    /* 略 */
}

補足

implementationだと通りませんでした。
バージョンについては、まず./gradlew dependenciesの出力からjakarta.xml.bindが含まれるものを探して、次にjakarta.xml.bind:jakarta.xml.bind-api:3.0.0 -> 2.3.3 (*)みたいな感じでバージョンが下がってるっぽい所を見つけ、より上のバージョンに合わせました。

バージョン類

  • nu.studer.jooq:7.1.1
  • org.jooq:jooq:3.16.4

【Intellij IDEA(Ultimate)】Run/Debug ConfigurationsのEnvironment variables(に限らず任意の項目)が見つからない際の対処

  • Run/Debug ConfigurationsEnvironment variables(に限らず任意の項目)は表示されない状態になっていることが有る
    • 特にSpring Bootで遭遇しやすいかも
  • Modify optionsから設定を行うことで表示することができる

画像付きの操作方法は↓の記事の一部としてまとめてあります。

qiita.com

【R2DBC】Could not resolve io.r2dbc:r2dbc-postgresqlへの対処

対処

io.r2dbc:r2dbc-postgresqlorg.postgresql:r2dbc-postgresqlに直せば通ります。

背景

r2dbc-postgresql0.9以降でリポジトリの移行が行われています。

github.com