同类中,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 的事务不生效。