以下を組み合わせて使うと例外が発生してもDBの更新がrollbackされないという事象に遭遇して、Springのソースコードを改めて読み直したのでメモしておく。

  • Kotlin Coroutine
  • SpringのTransactionalアノテーション
  • MyBatis

今回使用したバージョン

  • Kotlin: 1.9.24
  • kotlinx-coroutines: 1.8.1
  • Spring Boot: 3.3.1


発生した現象と原因

以下のようなコードの場合、

@Service
class FooService(
    private val fooRepository: FooRepository
) {
    @Transactional
    suspend fun upsert() {
        // DB更新
      fooRepository.update()
    }
}

以下のように処理が実行されていた。
coroutineの非同期処理を待ってcommit/rollbackをしてくれない。

トランザクション開始

すぐcommit

suspend関数の非同期処理を実行



Springのソースコード

トランザクション処理は、
org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
で行われている。



まとめ

Coroutine、SpringのTransactionalアノテーション、MyBatisを組み合わせると、トランザクションが意図したように動作しない理由をSpringのソースコードを読んで確認しました。

対応として以下のような選択肢があると思います。

  • R2DBC (Reactive Relational Database Connectivity)に対応したclientライブラリを使用する
  • Mybatisのような、非同期処理に対応していない(JDBCにしか対応していない)clientライブラリを使いたい場合はCoroutineを使わずに同期処理にする
    • 同期処理は処理の間1つのスレッドを専有してしまうので、十分なスレッド数を確保する事を忘れずに。