
Spring 的 @Transactional 注解依赖 AOP 代理机制生效,当事务方法被同一类中的其他方法直接调用时,会绕过代理对象,导致事务注解失效。
spring 的 `@transactional` 注解依赖 aop 代理机制生效,当事务方法被同一类中的其他方法直接调用时,会绕过代理对象,导致事务注解失效。
在你提供的代码中,transactionalError() 方法虽标注了 @Transactional,但它被同属 Test 类的 add() 方法(非事务方法)直接调用——这属于 self-invocation(自调用) 场景。此时 JVM 调用的是目标对象的原始方法,而非 Spring 生成的代理对象,因此事务切面(如事务开启、回滚逻辑)根本不会执行,"".substring(2) 抛出的 StringIndexOutOfBoundsException 无法触发事务回滚,数据仍会持久化到数据库。
而 add2() 方法本身加了 @Transactional,其内部调用 transactionalError() 虽仍是同对象调用,但由于整个 add2() 执行已在事务上下文中(由代理拦截并开启事务),且 transactionalError() 是普通方法调用(无事务传播要求),所以最终整个操作处于一个有效事务中;异常抛出后,Spring 事务管理器检测到未捕获的运行时异常,执行回滚——表现正常。
✅ 正确做法是将 @Transactional 方法移至独立的 Service 类中,确保调用方通过 Spring 容器注入的代理对象访问:
@Service
public class TransactionalService {
@Transactional
public void transactionalError(IMyService myService) {
MyObj myObj = new MyObj();
myService.save(myObj);
"".substring(2); // 触发 RuntimeException
MyObj myObj2 = new MyObj();
myService.save(myObj2);
}
}
@RestController
@RequestMapping("/test")
@RequiredArgsConstructor
public class Test {
private final IMyService myService;
private final TransactionalService transactionalService; // 注入代理Bean
@GetMapping("/1")
public void add() {
transactionalService.transactionalError(myService); // ✅ 经由代理调用
}
}
⚠️ 注意事项:
- @Transactional 默认仅对 未检查异常(RuntimeException 及其子类)和 Error 回滚;若需对受检异常回滚,需显式声明 @Transactional(rollbackFor = Exception.class);
- 确保目标类由 Spring 容器管理(使用 @Service/@Component 等),且调用方通过 @Autowired 或构造器注入,而非 new 实例化;
- 同一类内调用永远无法触发事务(无论是否 public),这是 Spring AOP 代理机制的根本限制,非配置或版本问题。
总结:事务不是“方法级魔法”,而是“代理拦截+上下文传播”的协作结果。将事务逻辑拆分到独立 Bean,是保障事务可靠性的最小实践原则。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











