Reactor驱动下必须显式开启ClientSession才能使用事务,所有操作需通过session.withTransaction()包裹并传入session实例,不可复用、不可跨订阅链、不可手动commit/abort。

Reactor 驱动下必须显式开启 ClientSession 才能使用事务
Reactor 版 MongoDB Java 驱动(mongodb-driver-reactivestreams + spring-boot-starter-data-mongodb-reactive)不支持自动事务上下文传播。你不能像 Spring 的 @Transactional 那样直接标注方法——事务生命周期完全由 ClientSession 控制,且必须手动传入每个操作。
常见错误是漏掉 withTransaction 或误用 startTransaction() 后忘记 commitTransaction() / abortTransaction(),导致会话卡住、连接泄漏或数据不一致。
-
ClientSession是线程绑定的,不可跨Mono/Flux订阅边界复用(比如在flatMap里重复用同一个 session 实例) - 事务内所有集合操作(
insertOne、updateOne等)必须通过session.withTransaction()包裹的 lambda 执行,不能单独调用 - Spring Data Reactive 不提供
@Transactional支持,强行加注解无效
withTransaction() 是唯一安全的事务入口,别手写 commit/abort 流程
驱动内部已封装重试逻辑和异常分类:只有抛出非 RuntimeException(如网络超时、主节点切换)才会触发自动重试;而业务校验失败(如 IllegalArgumentException)应主动 throw,让驱动 abort 并返回原始异常。
手写 session.startTransaction() → 操作 → session.commitTransaction() 是高危操作,一旦中间发生异常,commit 不会执行,但 session 可能仍处于 open 状态,后续请求会卡死或报 IllegalStateException: Session is in transaction。
立即学习“Java免费学习笔记(深入)”;
- 始终用
session.withTransaction(txBody),其中txBody是Function<clientsession mono>></clientsession> -
txBody内所有数据库操作必须调用带ClientSession参数的重载方法,例如:collection.insertOne(doc, options).publishOn(scheduler)❌;正确写法是collection.insertOne(session, doc, options)✅ - 事务内不能混合使用同步驱动操作或非 session 绑定的 reactive 操作
事务超时和读关注需在 TransactionOptions 中显式配置
MongoDB 默认事务超时为 60 秒,但 Reactive 驱动不会自动应用这个值——它只作用于服务端,客户端仍需配合设置操作级超时,否则长时间阻塞会拖垮整个事件循环。
另外,事务默认读关注(readConcern)是 "local",若需要强一致性(比如读自己刚写的文档),必须设为 "majority";同时要配匹配的写关注(writeConcern),否则事务可能因多数节点未确认而失败。
- 创建
TransactionOptions时用TransactionOptions.builder()显式指定:.readConcern(ReadConcern.MAJORITY)、.writeConcern(WriteConcern.MAJORITY) - 在
withTransaction()外层套timeout(Duration.ofSeconds(30)),防止事务卡死影响 reactor 线程 - 避免在事务中调用外部 HTTP 请求或阻塞 I/O,这会冻结整个 Netty event loop
嵌套 Mono/Flux 导致事务失效的典型场景
事务 session 无法穿透 flatMap、concatMap 或 zip 的订阅链。例如你在事务 lambda 里写 userService.findById(id).flatMap(u -> orderService.create(...)),第二个 service 调用若没把 session 透传进去,就会脱离事务上下文。
根本原因是 Reactor 的异步链不携带隐式上下文,ClientSession 是普通对象,不会自动“粘”在 Context 里(除非你自己用 contextWrite() 注入并提取,但驱动不支持)。
- 所有参与事务的操作必须在同一
withTransactionlambda 内完成,且每个操作都显式传入该 session 实例 - 不要试图把 session 存进
ThreadLocal或Context后跨链路取用——Reactor 的调度可能切换线程,且驱动 API 不读取这些上下文 - 复杂流程建议拆成原子事务块,用最终一致性+补偿代替长事务



















