MANDATORY 本质是运行时断言,要求方法必须在已有事务中执行,否则抛 IllegalTransactionStateException;它不创建事务,仅校验当前线程是否存在活跃事务,适用于必须依附外围事务的核心子流程。

要让 MANDATORY 传播行为真正生效,关键不是“确保调用方一定在事务中”,而是让调用链起点主动开启事务,并保证所有被 @Transactional(propagation = Propagation.MANDATORY) 标记的方法只被这些已开启事务的上下文调用。它本身不创建事务,只做校验——没事务就直接抛异常。
MANDATORY 的本质是契约式约束
它不是一个“自动保障机制”,而是一种运行时断言:方法声明“我只接受事务环境”。Spring 在进入该方法前检查当前是否存在事务上下文(即 TransactionSynchronizationManager.isActualTransactionActive()),若为 false,立即抛出 IllegalTransactionStateException。
- 它不关心事务是谁开的,只认当前线程绑定的事务资源
- 它不参与事务创建、挂起或嵌套,纯粹是“守门人”角色
- 典型适用场景:核心业务子流程(如扣款、发券),必须依附于外围完整业务事务,不允许独立执行
调用方必须显式启用事务
要避免异常,上游方法必须用 @Transactional(默认或显式 REQUIRED)包裹,并且该注解需生效(类被 Spring 管理、调用走代理、非 private/final 方法)。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 错误示例:ServiceA 中普通 public 方法直接调用标注 MANDATORY 的 ServiceB.methodB() —— 若 ServiceA 方法没事务,就会失败
- 正确做法:把调用逻辑放在 ServiceA 的
@Transactional方法里,或由 Controller 层的@Transactional方法发起(不推荐 Controller 加事务,应下沉到 Service) - 注意:同一类内 self-invocation(本类方法调用本类另一个
@Transactional方法)会绕过代理,导致事务失效,MANDATORY 同样会失败
验证和调试技巧
遇到 IllegalTransactionStateException 时,可快速定位问题:
立即学习“Java免费学习笔记(深入)”;
- 加日志:在 MANDATORY 方法入口打印
TransactionSynchronizationManager.isActualTransactionActive()结果 - 检查调用栈:确认最外层事务方法是否被 Spring AOP 正确拦截(比如是否用了 CGLIB 代理、是否满足代理条件)
- 单元测试时注意:
@Transactional测试方法自带事务,但若用@Commit或未配置事务管理器,也可能导致 MANDATORY 失效
替代方案要考虑清楚
如果发现强制要求上游事务太难协调,说明设计可能需要调整:
- 想“可选事务”?改用
SUPPORTS—— 有则用,无则不启 - 想“必须独立事务”?改用
REQUIRES_NEW—— 强制新开,挂起已有事务 - 想“允许嵌套回滚”?改用
NESTED—— 依赖 JDBC 保存点,非所有数据库都支持 - 仍坚持 MANDATORY,就需配套文档或接口契约,明确告知调用方:“此方法不可裸调,必须包在 @Transactional 方法内”

















