这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战
@Transactional
是我们在用Spring时候几乎逃不掉的一个注解,该注解主要用来声明事务。它的实现原理是通过Spring AOP在注解修饰方法的前后织入事务管理的实现语句,所以开发者只需要通过一个注解就能代替一系列繁琐的事务开始、事务关闭等重复性的编码任务。
一起看看,什么情况下@Transactional会失效,别再只会说:“我本地可以啊”~
在同一个类中调用
1 | typescript复制代码public class A { |
我们调用的方法A不带注解,所示是直接调用目标对象的方法。当进入目标对象的方法后,执行的上下文已经变成目标对象本身了,因为目标对象的代码是我们自己写的,和事务没有关系,此时你再调用带注解的方法,照样没有事务,只是一个普通的方法调用而已。简单来说,内部调用本类方法,不会再走代理了,所以B的事务不起作用。
看不懂吗?看不懂换个说法~
因为在A方法中调用B方法相当于使用this.B,this代表的是Service类本身,并不是真实的代理Service对象,这种不能实现代理功能。Spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,Spring 会为这个bean动态地生成一个子类(即代理类proxy),proxy是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由proxy来调用的,proxy在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过proxy,而是直接通过原来的那个bean,所以就不会启动transaction,即@Transactional注解无效。
解决方法
- 在xxxServiceImpl中,用(xxxService)(AopContext.currentProxy()),获取到xxxService的代理类,再调用事务方法,强行经过代理类,激活事务切面。
1 | typescript复制代码public class test { |
- @Autowired 注入自己来调用方法解决。
- 将事务方法放到另一个类中(或者单独开启一层,取名“事务层”)进行调用,即符合了在对象之间调用的条件。还是合理规划好层次关系即可,比如这样:
1 | less复制代码@Service |
@Transactional修饰方法不是public
1 | typescript复制代码public class TransactionalMistake { |
不同的数据源
1 | typescript复制代码public class TransactionalMistake { |
回滚异常配置不正确
默认情况下,仅对RuntimeException
和Error
进行回滚。如果不是的它们及它们的子孙异常的话,就不会回滚。
所以,在自定义异常的时候,要做好适当的规划,如果要影响事务回滚,可以定义为RuntimeException
的子类;如果不是RuntimeException
,但也希望触发回滚,那么可以使用rollbackFor
属性来指定要回滚的异常。
1 | java复制代码public class TransactionalMistake { |
数据库引擎不支持事务
例如漏了一个关键属性的配置:
1 | ini复制代码spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect |
这里的spring.jpa.database-platform
配置主要用来设置hibernate使用的方言。这里特地采用了MySQL5InnoDBDialect,主要为了保障在使用Spring Data JPA时候,Hibernate自动创建表的时候使用InnoDB存储引擎,不然就会以默认存储引擎MyISAM来建表,而MyISAM存储引擎是没有事务的。
如果你的事务没有生效,那么可以看看创建的表,是不是使用了MyISAM存储引擎,如果是的话,那就是这个原因了!
参考:
(1)www.cnblogs.com/guobi777/p/…
(2)mp.weixin.qq.com/s/oblTiQnvp…
本文转载自: 掘金