spring事务@Transactional失效原因及解决办法小结
作者:程序猿(攻城狮)
spring事务@Transactional失效情况分析主要从以下几个方面考虑:
1. mysql数据库
默认情况下mysql数据库使用的是Innodb存储引擎(5.5版本之后),它是支持事务的,但是如果你的表的存储引擎是MyISAM,MyISAM是不支持事务的。这样就会出现“事务失效”的问题了。
解决方案:修改存储引擎为Innodb。
2. 业务代码
2.1 执行事务的Bean交由Spring管理
我们要使用Spring的申明式事务,那么需要执行事务的Bean是否已经交由了Spring管理,在代码中的体现就是类上是否有@Service、Component等一系列注解。
解决方案:将Bean交由Spring进行管理(添加@Service注解)
2.2 非public的方法进行事务管理
默认情况下你无法使用@Transactional
对一个非public的方法进行事务管理
解决方案:修改需要事务管理的方法为public
。
2.3 出现了自调用
多个方法都在同一个类中,其中第一个方法中调用了本类中的第二个和第三个方法,这就是自调用。
那么自调用为什么会导致事务失效呢?我们知道Spring中事务的实现是依赖于AOP的,当容器在创建Service这个Bean时,发现这个类中存在了被@Transactional标注的方法(修饰符为public)那么就需要为这个类创建一个代理对象并放入到容器中。由于方法实际上是由Service也就是目标类自己调用的,所以在方法的前后并不会执行事务的相关操作。这也是自调用带来问题的根本原因:自调用时,调用的是目标类中的方法而不是代理类中的方法。
解决方案:
- 自己注入自己,然后显示的调用,
- 这种方案看起来不是很优雅
- 利用
AopContext
,如下:
@Service public class DemoService { @Transactional public void save(A a, B b) { ((DemoService) AopContext.currentProxy()).saveB(b); } @Transactional(propagation = Propagation.REQUIRES_NEW) public void saveB(B b){ dao.saveB(a); } }
使用上面这种解决方案需要注意的是,需要在配置类上新增一个配置
// exposeProxy=true代表将代理类放入到线程上下文中,默认是false @EnableAspectJAutoProxy(exposeProxy = true)
3. 事务回滚相关问题
3.1 该回滚的时候事务提交了
这种情况往往是程序员对Spring中事务的rollbackFor属性不够了解导致的。
Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务,已经执行的SQL会提交掉。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定rollbackFor属性。
默认情况下,只有出现RuntimeException或者Error才会回滚
public boolean rollbackOn(Throwable ex) { return (ex instanceof RuntimeException || ex instanceof Error); }
所以,如果你想在出现了非RuntimeException或者Error时也回滚,请指定回滚时的异常,例如:
@Transactional(rollbackFor = Exception.class)
3.2 该提交的事务被回滚
对应的异常信息如下:
Transaction rolled back because it has been marked as rollback-only
总结起来,主要的原因就是因为内部事务回滚时将整个大事务做了一个rollbackOnly的标记,所以即使我们在外部事务中catch了抛出的异常,整个事务仍然无法正常提交,并且如果你希望正常提交,Spring还会抛出一个异常。
3.3 解决方案
这个解决方案要依赖业务而定,你要明确你想要的结果是什么
内部事务发生异常,外部事务catch异常后,内部事务自行回滚,不影响外部事务
将内部事务的传播级别设置为nested/requires_new均可。在我们的例子中就是做如下修改:
// @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW) @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED) public void a() throws ClassNotFoundException{ // ...... throw new ClassNotFoundException(); }
虽然这两者都能得到上面的结果,但是它们之间还是有不同的。当传播级别为requires_new时,两个事务完全没有联系,各自都有自己的事务管理机制(开启事务、关闭事务、回滚事务)。但是传播级别为nested时,实际上只存在一个事务,只是在调用a方法时设置了一个保存点,当a方法回滚时,实际上是回滚到保存点上,并且当外部事务提交时,内部事务才会提交,外部事务如果回滚,内部事务会跟着回滚。
内部事务发生异常时,外部事务catch异常后,内外两个事务都回滚,但是方法不抛出异常
4. 读写分离跟事务结合使用时的问题
读写分离一般有两种实现方式
- 配置多数据源
- 依赖中间件
如果是配置了多数据源的方式实现了读写分离,那么需要注意的是:如果开启了一个读写事务,那么必须使用写节点,如果是一个只读事务,那么可以使用读节点。
如果是依赖于中间件那么需要注意:需要根据中间件的事务规范使用事务。
到此这篇关于spring事务@Transactional失效原因及解决办法小结的文章就介绍到这了,更多相关spring @Transactional失效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!