MongoDB事务使用SessionScoped MongoTemplate触发WriteConflict错误咨询
Spring Data MongoDB 完全支持绑定 ClientSession 的 MongoTemplate 执行事务操作,你遇到的报错是代码实现和配置缺失导致的,和框架本身的支持性无关。
问题根因
- ThreadLocal 使用方式错误
你在代码中每次执行逻辑时都会给成员变量sessionMongoTemplate重新赋值新的 ThreadLocal 实例,而 Spring 管理的 Bean 默认都是单例,多线程场景下会互相覆盖 ThreadLocal 实例,导致不同线程拿到的 ClientSession 错乱,操作无法落到当前事务的会话上,自然会触发写冲突。 - 缺少事务管理器配置
Spring Data MongoDB 需要显式注入MongoTransactionManager才能开启事务上下文识别,你没有配置的情况下,就算手动将 MongoTemplate 和 Session 绑定,操作也会以非事务的普通请求发送到服务端,相当于不同的请求在无事务的情况下修改同一份数据,触发 WriteConflict。 - 缺少事务重试逻辑
你遇到的携带TransientTransactionError标签的 WriteConflict 是 MongoDB 事务的典型瞬态错误,是并发修改同一份文档时的正常报错,MongoDB 官方要求遇到这类错误必须重试事务,你没有实现重试逻辑的情况下就会直接抛出异常。
解决方案
第一步:添加事务管理器配置
在 Spring Boot 配置类中注入事务管理器:
import org.springframework.context.annotation.Bean; import org.springframework.data.mongodb.MongoDatabaseFactory; import org.springframework.data.mongodb.MongoTransactionManager; @Configuration public class MongoConfig { @Bean public MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) { return new MongoTransactionManager(dbFactory); } }
第二步:修正代码实现
调整 ThreadLocal 的使用逻辑,添加事务重试和资源清理逻辑:
// 成员变量仅初始化一次 ThreadLocal,不要每次执行逻辑时重新实例化 private final ThreadLocal<MongoOperations> sessionMongoTemplate = new ThreadLocal<>(); public void doTransactionalBusiness() { int maxRetryTimes = 3; try (final ClientSession session = getClientSession()) { // 给当前线程绑定关联了 session 的 MongoOperations sessionMongoTemplate.set(getMongoOperations().withSession(session)); // 自定义事务选项,可根据业务调整读写策略 TransactionOptions txnOptions = TransactionOptions.builder() .readPreference(ReadPreference.primary()) .readConcern(ReadConcern.MAJORITY) .writeConcern(WriteConcern.MAJORITY) .build(); // 瞬态错误重试逻辑 int retryCnt = 0; while (retryCnt < maxRetryTimes) { try { session.withTransaction(() -> { // 你的业务逻辑,直接调用 sessionMongoTemplate.get() 执行增删改查 sessionMongoTemplate.get().insert(entity); return null; }, txnOptions); break; } catch (MongoCommandException e) { // 仅对瞬态事务错误重试 if (e.getErrorLabels().contains("TransientTransactionError") && retryCnt < maxRetryTimes -1) { retryCnt++; Thread.sleep(100); continue; } throw e; } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("事务执行中断", e); } finally { // 必须清理 ThreadLocal,避免内存泄漏和线程复用导致的会话错乱 sessionMongoTemplate.remove(); } }
其他建议
如果不想手动管理会话和事务,也可以直接使用 Spring 提供的声明式事务,只需要在事务方法上添加 @Transactional 注解,Spring 会自动完成会话绑定、事务提交/回滚、重试等逻辑,代码更简洁,出错概率更低。
内容的提问来源于stack exchange,提问作者amit.shipra
相关产品推荐
相关产品推荐

