同类中,A中调用B时,B的事务是否会生效!
同类中,A中调用B时,B的事务是否会生效!
月伴飞鱼在 Spring 的声明式事务(@Transactional)语境里,同一个类内部 A 调用 B,B 上的事务通常不会按预期生效。
核心原因是:事务是通过 AOP 代理(Proxy)在方法调用边界处织入的,而类内自调用属于 this直接调用,绕过了代理。
事务是否“生效”,取决于调用是否经过代理
代理调用:会触发事务拦截器
外部对象调用
serviceA.methodB()时,调用入口是代理对象。
TransactionInterceptor(事务拦截器)有机会在进入方法前开启/加入事务,在退出时提交/回滚。
类内自调用:不会触发事务拦截器
在同一类中,A 里写
this.B()或直接B(),调用不会经过代理,事务拦截器不执行,因此 B 上的@Transactional不会被应用。此时 B 代码运行在什么事务语境里,只取决于 A(或更外层)是否已经开启了事务。
常见组合下的实际行为
1)A 有事务,A 调 B(同类自调用)
- B 的
@Transactional注解 不生效(传播行为、隔离级别、超时、只读等配置都不会在 B 处生效)。 - B 会在 A 已经开启的事务中继续执行(等价于“没有单独声明事务的普通方法被 A 调用”)。
- 若 B 抛出运行时异常并向外冒泡,最终是否回滚主要看 A 的事务规则(例如
rollbackFor配置在 A 上时才会影响)。
2)A 无事务,B 有事务,A 调 B(同类自调用)
- B 的事务 不会开启,因为注解没被拦截器触发。
- B 将以 非事务方式 执行(取决于数据源自动提交、MyBatis/JPA 的 flush 时机等)。
3)A 有事务,B 标注 REQUIRES_NEW,A 调 B(同类自调用)
- 预期是“挂起 A,开启新事务执行 B”,但由于 B 注解不生效:
- 不会创建新事务,也不会挂起 A,B 仍在 A 的同一个事务 中运行。
4)A 调用的是另一个 Bean 的 B(跨 Bean 调用)
- 若
B在另一个 Spring Bean 上,通过注入调用(otherService.B()) - 调用会经过代理,B 的事务 会生效,传播行为也会按配置执行。
典型误区与边界
- **误区:A 和 B 都标了 **
@Transactional,就一定各自生效。- 实际上只要是同类内部直接调用,B 的注解就像“写在注释里”,不会触发事务织入。
- 边界:前提是使用 Spring AOP 代理的声明式事务。
- 若使用 AspectJ 编织(较少见)或其他字节码增强方式,行为可能不同;但大多数 Spring 项目默认是代理机制。
让 B 的事务真正生效的常用做法
- 拆分到不同 Bean:把 B 移到独立的
@Service,A 注入后调用,保证走代理。 - 通过代理调用自身:例如注入自身代理(
@Lazy注入自身接口)或使用AopContext.currentProxy()(需要开启exposeProxy),再用代理去调用 B。- 这类做法可行但侵入性更强,工程上更常用“拆 Bean”。
以上规则可以简化成一句:事务生效看“入口”是否经过代理;同类内部自调用通常不经过代理,因此 B 的事务不生效。














