You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin协程中使用withContext切换安全上下文时如何保留事务?

问题原因

你遇到的问题本质是Reactor Context被完全覆盖,导致事务上下文丢失:

  • Spring的反应式事务是通过Reactor Context传递事务状态的,@Transactional注解会在方法执行时将事务相关的上下文存入Reactor Context中。
  • 当你直接用ReactorContext(...)创建新的上下文并传入withContext时,会替换掉原有的整个Reactor Context,包括里面的事务上下文。这就导致withContext块内的数据库操作不在原事务中执行,自然看不到之前repo.save(Some())的未提交数据(原事务未提交时,非原事务的查询无法读取到未提交的脏数据)。
  • 而使用withContext(Dispatchers.IO)时,只是切换了协程的调度器,不会覆盖原有的Reactor Context,事务上下文得以保留,所以查询能正常读取到原事务内的数据。
解决方案

不要直接替换整个Reactor Context,而是在原有上下文基础上添加/更新SecurityContext,确保事务上下文不丢失。有两种常用方式:

方式一:合并原有Reactor Context

在创建新的ReactorContext时,先获取原有的上下文内容,再合并进SecurityContext:

@Transactional
suspend fun foo() {
    val a = repo.save(Some())
    println(repo.count()); // 输出1

    // 获取当前协程中的原有Reactor Context
    val originalReactorContext = coroutineContext[ReactorContext]?.context ?: Context.empty()
    // 合并原有上下文与新的SecurityContext
    val newContext = originalReactorContext.putAll(
        ReactiveSecurityContextHolder.withAuthentication(
            RunAsUserToken(
                it.userId.toString(),
                it.userId,
                it.userId,
                mutableListOf(),
                AbstractAuthenticationToken::class.java,
            )
        )
    )

    withContext(ReactorContext(newContext)) {
        println(repo.count()); // 输出1,事务上下文保留
    }
}

方式二:使用reactorContext扩展函数(更简洁)

借助kotlinx-coroutines-reactor提供的扩展函数,直接修改原有Reactor Context:

import kotlinx.coroutines.reactor.reactorContext

@Transactional
suspend fun foo() {
    val a = repo.save(Some())
    println(repo.count()); // 输出1

    withContext(reactorContext { ctx ->
        // 在原有上下文基础上添加新的SecurityContext
        ctx.putAll(
            ReactiveSecurityContextHolder.withAuthentication(
                RunAsUserToken(
                    it.userId.toString(),
                    it.userId,
                    it.userId,
                    mutableListOf(),
                    AbstractAuthenticationToken::class.java,
                )
            )
        )
    }) {
        println(repo.count()); // 输出1,事务上下文保留
    }
}
关键要点
  • 反应式事务依赖Reactor Context传递状态,任何对Reactor Context的替换操作都会导致事务丢失。
  • 协程中使用withContext时,若要修改上下文,务必基于原有上下文进行合并,而非完全替换。

内容的提问来源于stack exchange,提问作者Stuck

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 18:12:32