完美解决Spring声明式事务不回滚的问题
疑问,确实像往常一样在service上添加了注解 @transactional,为什么查询数据库时还是发现有数据不一致的情况,想想肯定是事务没起作用,出现异常的时候数据没有回滚。于是就对相关代码进行了一番测试,结果发现一下踩进了两个坑,确实是事务未回滚导致的数据不一致。
下面总结一下经验教训:
spring事务的管理操作方法
编程式的事务管理
实际应用中很少使用
通过使用transactiontemplate 手动管理事务
声明式的事务管理
开发中推荐使用(代码侵入最少)
spring的声明式事务是通过aop实现的
主要掌握声明式的事务管理。
spring事务不回滚的两个原因
总结一下导致事务不回滚的两个原因,一是service类内部方法调用,二是try...catch异常。
1. service类内部方法调用
大概就是 service 中有一个方法 a,会内部调用方法 b, 方法 a 没有事务管理,方法 b 采用了声明式事务,通过在方法上声明 transactional 的注解来做事务管理。示例代码如下:
@service public class rabbitserviceimpl implements rabbitservice { @autowired private rabbitdao rabbitdao; @autowired private tortoisedao tortoisedao; @override public rabbit methoda(string name){ return methodb(name); } @transactional(propagation = propagation.required) public boolean methodb(string name){ rabbitdao.insertrabbit(name); tortoisedao.inserttortoise(name); return true; } }
单元测试代码如下:
public class rabbitserviceimpltest { @autowired private rabbitservice rabbitservice; // 事务未开启 @test public void testa(){ rabbitservice.methoda("rabbit"); } // 事务开启 @test public void testb(){ rabbitservice.methodb("rabbit"); } }
从上一节中可以看到,声明式事务是通通过aop动态代理实现的,这样会产生一个代理类来做事务管理,而目标类(service)本身是不能感知代理类的存在的。
对于加了@transactional注解的方法来说,在调用代理类的方法时,会先通过拦截器transactioninterceptor开启事务,然后在调用目标类的方法,最后在调用结束后,transactioninterceptor 会提交或回滚事务,大致流程如下图:
总结,在方法 a 中调用方法 b,实际上是通过“this”的引用,也就是直接调用了目标类的方法,而非通过 spring 上下文获得的代理类,所以事务是不会开启的。
2. try...catch异常
在一段业务逻辑中对数据库异常进行了处理,使用了try...catch子句捕获异常并throw了一个自定义异常,这种情况导致了事务未回滚,示例代码如下:
@transactional(propagation = propagation.required) public boolean methodb(string name) throws bizexception { try { rabbitdao.insertrabbit(name); tortoisedao.inserttortoise(name); } catch (exception e) { throw new bizexception(returncode.exception.code, returncode.exception.msg); } return true; }
bizexception的定义如下:
public class bizexception extends exception { // 自定义异常 }
上面代码中的声明式事务在出现异常的时候,事务是不会回滚的。在代码中我虽然捕获了异常,但是同时我也抛出了异常,为什么事务未回滚呢?猜测是异常类型不对,于是开始查询原因,翻看了spring的官方文档,找到了答案。下面是翻译自spring官网。
17.5.3 声明式事务的回滚
上一节中介绍了如何设置开启spring事务,一般在你的应用的service层代码中设置,这一节将介绍在简单流行的声明式事务中如何控制事务回滚。
在spring framework 的事务框架中推荐的事务回滚方法是,在当前执行的事务上下文中抛出一个异常。如果异常未被处理,当抛出异常调用堆栈的时候,spring framework 的事务框架代码将捕获任何未处理的异常,然后并决定是否将此事务标记为回滚。
在默认配置中,spring framework 的事务框架代码只会将出现runtime, unchecked 异常的事务标记为回滚;也就是说事务中抛出的异常时runtimeexception或者是其子类,这样事务才会回滚(默认情况下error也会导致事务回滚)。在默认配置的情况下,所有的 checked 异常都不会引起事务回滚。
注:unchecked exception包括error与runtimeexception. runtimeexception的所有子类也都属于此类。另一类就是checked exception。
你可以精确的配置异常类型,指定此异常类事务回滚,包括 checked 异常。下面的xml代码片段展示了如何配置checked异常引起事务回滚,应用自定义异常类型:
<tx:advice id="txadvice" transaction-manager="txmanager"> <tx:attributes> <tx:method name="get*" read-only="true" rollback-for="exception"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
与其有同等作用的注解形式如下:
@transactional(rollbackforclassname={"exception"}) 或者 @transactional(rollbackfor={exception.class})
在你遇到异常不想回滚事务的时候,同样的你也可指定不回滚的规则,下面的一个例子告诉你,即使遇到未处理的 instrumentnotfoundexception 异常时,spring framework 的事务框架同样会提交事务,而不回滚。
<tx:advice id="txadvice"> <tx:attributes> <tx:method name="updatestock" no-rollback-for="instrumentnotfoundexception"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
与其有同样作用的注解形式如下:
@transactional(norollbackforclassname={"instrumentnotfoundexception"}) 或者 @transactional(norollbackfor={instrumentnotfoundexception.class})
还有更灵活的回滚规则配置方法,同时指定什么异常回滚,什么异常不回滚。当spring framework 的事务框架捕获到一个异常的时候,会去匹配配置的回滚规则来决定是否标记回滚事务,使用匹配度最强的规则结果。因此,下面的配置例子表达的意思是,除了异常 instrumentnotfoundexception 之外的任何异常都会导致事务回滚。
<tx:advice id="txadvice"> <tx:attributes> <tx:method name="*" rollback-for="throwable" no-rollback-for="instrumentnotfoundexception"/> </tx:attributes> </tx:advice>
你也可以通过编程式的方式回滚一个事务,尽管方法非常简单,但是也有非常强的代码侵入性,使你的业务代码和spring framework 的事务框架代码紧密的绑定在一起,示例代码如下:
public void resolveposition() { try { // some business logic... } catch (noproductinstockexception ex) { // trigger rollback programmatically transactionaspectsupport.currenttransactionstatus().setrollbackonly(); } }
如果可能的话,强烈推荐您使用声明式事务方式回滚事务,对于编程式事务,如果你强烈需要它,也是可以使用的,but its usage flies in the face of achieving a clean pojo-based architecture.(没懂...)
看完官方文档这节内容找到了问题的答案,原来是因为我们自定义的异常不是 runtimeexception。我的解决办法是,在注解@transactional中添加 rollbackfor={bizexception.class}。可能你会问我为什么不将自定义异常修改为继承runtimeexception,因为我需要bizexception是一个checked 异常。
以上这篇完美解决spring声明式事务不回滚的问题就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。