mongodb 本身没有 @transactional 注解,失效源于 spring aop 代理机制;自调用(this.methodb())绕过代理,事务未触发,与 mongodb 配置无关;可行方案为拆类、手动获取代理或改用编程式事务。

MongoDB 本身没有 @Transactional 注解,这个失效问题完全来自 Spring AOP 的代理机制,和 MongoDB 驱动、事务配置、副本集状态都无关。
Spring 的 @Transactional 为什么只对“外部调用”生效
Spring 默认使用 JDK 动态代理(接口代理)或 CGLIB(类代理)来织入事务逻辑。关键点在于:代理对象必须被 Spring 容器管理,并且调用必须经过代理层。
- 当你在同一个类里写
methodA()调用methodB(),而methodB()上有@Transactional,这属于this.methodB() 调用,走的是原始对象的内部方法跳转,根本没经过代理对象 - Spring 只能拦截从容器外发起的调用(比如 Controller → Service),无法拦截 Service 内部的
this.调用 - 即使你把类改成 CGLIB 代理(
@EnableAspectJAutoProxy(proxyTargetClass = true)),自调用依然不生效——因为字节码层面仍是直接调用,没触发代理的invoke()
常见误判:以为是 MongoDB 事务没开或配置错
很多人看到自调用下事务没回滚,第一反应是查 replSetGetStatus、翻 writeConcern、重配 mongod --replSet,其实全是白忙——@Transactional 根本没被触发,MongoDB 连事务命令都没收到。
- 验证方式很简单:在
methodB()开头打个断点,看是否进入;再看session.startTransaction()是否被调用(用驱动日志或 WireShark 抓包) - 如果断点进了但
db.currentOp({ "secs_running": { "$gt": 0 } })里看不到活跃事务,基本可确认是 AOP 没织入 -
CommandNotSupportedOnServer或Transaction numbers are only allowed on a replica set member是服务端拒绝,而自调用失效时连这些错误都不会出现
真正能用的三种绕过方案
不是“怎么修自调用”,而是“怎么绕过它”。别试图让 this.methodB() 变成代理调用——技术上不可靠,维护成本高。
- 拆类:把带
@Transactional的逻辑抽到另一个@Service类里,通过构造器注入调用,确保走 Spring 代理 - 手动获取代理:在当前类里注入
ApplicationContext,用context.getBean(ThisService.class).methodB()——但要注意循环依赖风险,且破坏了测试友好性 - 放弃声明式,改用编程式事务:
TransactionTemplate或TransactionManager+TransactionStatus,在methodA()里显式控制beginTransaction()/commit(),完全脱离 AOP 限制
最容易被忽略的底层事实
Spring 的事务代理和 MongoDB 的事务是两层独立机制:前者决定“要不要发 startTransaction 命令”,后者决定“发了之后能不能执行”。自调用失效发生在第一层,还没轮到 MongoDB 发挥作用。哪怕你本地跑的是 6 节点分片集群、writeConcern={w:"majority"}、readConcern="snapshot" 全配齐,this.methodB() 里的数据库操作照样是自动提交的普通操作。











