Spring中@Transactional注解方法是否及何时锁表?事务失效问题咨询
首先得搞懂Spring事务的工作原理:Spring的@Transactional注解是基于动态代理实现的。当你调用一个被Spring管理的Bean的方法时,实际上调用的是代理类的方法,代理类会在目标方法执行前后加上事务的开启、提交/回滚等逻辑。
但如果是同一个类内部的方法调用(比如你的methodA调用methodB),这时候调用的是原始对象的方法,根本没经过代理类,所以methodB上的@Transactional注解完全不会触发,抛出异常后自然不会回滚DB操作。
下面给你几种常用的解决方案:
方案1:把事务方法抽离到独立的Bean中
这是最规范也最推荐的方式——把带@Transactional的methodB放到另一个被Spring管理的类里,然后在classA中注入这个类,通过注入的实例调用methodB,这样就会走代理逻辑,事务生效。
示例代码:
// 新建一个专门处理DB事务的类 @Service public class DbService { @Transactional public void methodB() { // insert a record throw new RuntimeException("testing"); } } // 修改后的classA @Service public class ClassA { private final DbService dbService; // 构造方法注入(推荐) public ClassA(DbService dbService) { this.dbService = dbService; } public void methodA() { // some code ... dbService.methodB(); // 这里调用的是代理对象的方法,事务会生效 } }
方案2:获取自身的代理对象调用事务方法
如果不想拆分类,可以通过Spring提供的工具获取当前类的代理对象,然后用代理对象调用methodB。
注意:使用这种方式需要先开启代理对象暴露功能,在你的启动类或者配置类上添加@EnableAspectJAutoProxy(exposeProxy = true)。
示例代码:
@Service public class ClassA { public void methodA() { // some code ... // 获取当前类的代理对象,然后调用methodB ((ClassA) AopContext.currentProxy()).methodB(); } @Transactional public void methodB() { // insert a record throw new RuntimeException("testing"); } }
另外也可以通过注入ApplicationContext来获取自身的代理对象,效果是一样的:
@Service public class ClassA { private final ApplicationContext applicationContext; public ClassA(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } public void methodA() { // some code ... ClassA proxy = applicationContext.getBean(ClassA.class); proxy.methodB(); } @Transactional public void methodB() { // insert a record throw new RuntimeException("testing"); } }
方案3:给外层方法添加@Transactional注解
如果你的methodA本身也需要事务,或者可以接受整个methodA的逻辑都处于同一个事务中,那直接给methodA加上@Transactional注解就行。这样整个methodA的执行都会在事务上下文里,methodB抛出异常时,整个事务会回滚。
不过要注意:这种方式会让methodA里的所有操作都共享同一个事务,如果methodA有不需要事务的耗时操作,可能会导致事务超时或者资源占用问题,需要根据实际业务场景判断是否适用。
示例代码:
@Service public class ClassA { @Transactional public void methodA() { // some code ... methodB(); } // 这里可以保留@Transactional,也可以去掉,因为外层已经有事务了 public void methodB() { // insert a record throw new RuntimeException("testing"); } }
总结一下:Spring事务生效的关键是通过代理对象调用带注解的方法,内部直接调用会绕过代理,导致注解失效。上面的几种方案都是围绕这个核心来解决问题的。
内容的提问来源于stack exchange,提问作者xuanzhui

