必须显式调用commitTransaction()或abortTransaction()配对startTransaction(),二者须置于finally块中确保执行;所有CRUD操作必须传入同一ClientSession实例且不可跨线程复用;事务内异常需区分处理,业务异常应直接throw RuntimeException触发自动abort。

startTransaction()之后必须配对调用commitTransaction()或abortTransaction()
Java原生驱动中事务不会自动提交,也不会自动回滚。startTransaction()只是把会话标记为“事务中”,后续所有操作都暂存,但不落盘;真正生效与否,完全取决于你是否显式调用commitTransaction()或abortTransaction()。
常见错误现象:commitTransaction()抛异常后没处理,导致会话卡在 OPEN 状态,后续再用该 session 会直接报 IllegalStateException: Session is in transaction;或者漏写 finally 块,session 没关闭,连接池逐渐耗尽。
- 必须把
commitTransaction()和abortTransaction()放在try-catch-finally的finally块里,确保至少执行其一 - 不要在
commitTransaction()失败后静默吞掉异常——它可能因写冲突、网络中断、权限不足等失败,此时应主动abortTransaction() - 一个
ClientSession只能调用一次startTransaction();重复调用会抛IllegalStateException -
abortTransaction()后 session 不可再用,再次使用会抛InvalidSessionException
事务内所有操作必须显式传入session参数
MongoDB 不会自动把当前线程绑定的 session 注入到 insertOne()、updateOne() 等方法中。你必须手动把 ClientSession 实例作为第一个参数传进去,否则操作会脱离事务上下文,变成普通非事务写入。
典型错误:写了 session.startTransaction(),但调用 collection.insertOne(document)(没传 session),结果这个插入立刻生效,且无法被后续 abortTransaction() 回滚。
- 所有 CRUD 方法只要带
session重载版本,就必须用它:例如collection.insertOne(session, document),而不是collection.insertOne(document) - 读操作也要传 session 才能参与因果一致性保障,比如
collection.find(session, Filters.eq("x", 1)) - 不同 collection 的操作可以混在同一事务中,但所有调用都必须共用同一个
ClientSession实例
ClientSession不可跨线程复用,也不可被Spring @Transactional接管
原生 Java 驱动的 ClientSession 是线程绑定的,不是 Spring 的代理对象。你在主线程创建的 session,不能在 ForkJoinPool、CompletableFuture 或异步回调中直接使用——哪怕只读取字段也会出错。
更关键的是:@Transactional 注解对 MongoDB 原生驱动完全无效。Spring Data MongoDB 的 @Transactional 仅适配了同步模板(如 MongoTemplate)且需开启事务管理器;而你手写的 ClientSession + startTransaction() 流程,Spring 不感知、不传播、不拦截。
- 别试图把
ClientSession存在 ThreadLocal 里跨异步边界传递——驱动内部有状态校验,大概率触发IllegalStateException - 如果要用异步逻辑,每个子任务必须自己
client.startSession(),并独立管理生命周期(注意资源开销) - 事务超时靠服务端控制(默认 60 秒),但依赖超时回滚是危险策略;必须主动
abortTransaction(),不能等它过期
事务失败时的异常类型决定是否重试,业务异常要主动throw
驱动对事务内异常做了分类处理:RuntimeException(如 IllegalArgumentException、NullPointerException)不会触发自动重试,而是立即 abort;而非 RuntimeException(如 MongoTimeoutException、MongoSocketReadException)才会由驱动内部按配置重试(默认最多 1 次)。
这意味着:业务校验失败(比如库存不足、金额为负)应该直接 throw new IllegalArgumentException("balance too low"),让驱动干净地 abort 并返回原始异常;而不是 try-catch 吞掉再手动 abort——多此一举还容易漏掉清理逻辑。
- 在
withTransaction()lambda 内 throw 运行时异常,驱动会捕获并 abort,无需你写abortTransaction() - 但如果你用的是手动
startTransaction()→ 操作 →commitTransaction()流程,就仍需自己 catch 异常并调用abortTransaction() - 事务内禁止调用阻塞 I/O(如 HTTP 请求、文件读写),否则可能拖垮整个事务超时窗口
事务生命周期的手动控制,核心就三点:session 必须显式传参、commit/abort 必须成对兜底、异常路径必须区分对待。最容易被忽略的是 session 的线程绑定特性——它不像数据库连接那样可池化复用,每次 startSession() 都是新实例,用完就得 close(),否则就是隐性泄漏。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











