声明式事务是Spring功能中最爽之一,但是有些时候,咱们在使用声明式事务并未生效,这是为何呢?面试
今天陈某带你们来聊一聊声明事务的几种失效场景。本文将会从如下两个方面来讲一下事务为何会失效?数据库
@Transactional介绍mvc
@Transactional失效场景ide
@Transactional
是声明式事务的注解,能够被标记在类上
、接口
、方法
上。ui
该注解中有不少值得深刻了解的几种属性,咱们来看一下。this
指定事务管理器,值为bean
的名称,这个主要用于多事务管理器状况下指定。好比多数据源配置的状况下。spa
事务的隔离级别,默认是Isolation.DEFAULT
。代理
几种值的含义以下:code
Isolation.DEFAULT
:事务默认的隔离级别,使用数据库默认的隔离级别。component
Isolation.READ_UNCOMMITTED
:这是事务最低的隔离级别,它充许别外一个事务能够看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。
Isolation.READ_COMMITTED
:保证一个事务修改的数据提交后才能被另一个事务读取。另一个事务不能读取该事务未提交的数据。这种事务隔离级别能够避免脏读出现,可是可能会出现不可重复读和幻读。
Isolation.REPEATABLE_READ
:这种事务隔离级别能够防止脏读,不可重复读。可是可能出现幻读。
Isolation.SERIALIZABLE
:这是花费最高代价可是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。
表明事务的传播行为,默认值为Propagation.REQUIRED
。
Propagation.REQUIRED
:若是存在一个事务,则支持当前事务。若是没有事务则开启一个新的事务。好比A方法内部调用了B方法,此时B方法将会使用A方法的事务。
Propagation.MANDATORY
:支持当前事务,若是当前没有事务,就抛出异常。
Propagation.NEVER
:以非事务方式执行,若是当前存在事务,则抛出异常。
Propagation.NOT_SUPPORTED
:以非事务方式执行操做,若是当前存在事务,就把当前事务挂起。
Propagation.REQUIRES_NEW
:新建事务,若是当前存在事务,把当前事务挂起。好比A方法使用默认的事务传播属性,B方法使用REQUIRES_NEW
,此时A方法在内部调用B方法,一旦A方法出现异常,A方法中的事务回滚了,可是B方法并无回滚,由于A和B方法使用的不是同一个事务,B方法新建了一个事务。
Propagation.NESTED
:支持当前事务,新增Savepoint
点,也就是在进入子事务以前,父事务创建一个回滚点,与当前事务同步提交或回滚。子事务是父事务的一部分,在父事务还未提交时,子事务必定没有提交。嵌套事务一个很是重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所作的动做。而内层事务操做失败并不会引发外层事务的回滚。
事务的超时时间,单位为秒。
该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。若是一个事务只涉及到只读,能够设置为true。
用于指定可以触发事务回滚的异常类型,能够指定多个异常类型。
默认是在RuntimeException
和Error
上回滚。
抛出指定的异常类型,不回滚事务,也能够指定多个异常类型。
声明式事务失效的场景有不少,陈某这里只是罗列一下几种常见的场景。
若是数据库引擎不支持事务,则Spring天然没法支持事务。
@Transactional注解使用的是AOP,在使用动态代理的时候只能针对public
方法进行代理,源码依据在AbstractFallbackTransactionAttributeSource
类中的computeTransactionAttribute
方法中,以下:
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; }
此处若是不是标注在public
修饰的方法上并不会抛出异常,可是会致使事务失效。
这种状况小白是最容易犯错的,在整个事务的方法中使用try-catch
,致使异常没法抛出,天然会致使事务失效。伪代码以下:
@Transactional public void method(){ try{ //插入一条数据 //更改一条数据 }catch(Exception ex){ return; } }
简单的说就是一个类中的A方法
(未标注声明式事务)在内部调用了B方法
(标注了声明式事务),这样会致使B方法中的事务失效。
代码以下:
public class Test{ public void A(){ //插入一条数据 //调用B方法 B(); } @Transactional public void B(){ //插入数据 } }
为何会失效呢?:其实缘由很简单,Spring在扫描Bean的时候会自动为标注了@Transactional
注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,其实是代理类调用的,代理类在调用以前会开启事务,执行事务的操做,可是同类中的方法互相调用,至关于this.B()
,此时的B方法并不是是代理类调用,而是直接经过原有的Bean直接调用,因此注解会失效。
如何解决呢?:这就涉及到注解失效的缘由了,后续文章会介绍到,这里不过多介绍了。
很容易理解,指定异常触发回滚,一旦设置错误,致使一些异常不能触发回滚,此时的声明式事务不就失效了吗。
这个和rollbackFor属性设置错误相似,一旦设置错误,也会致使异常不能触发回滚,此时的声明式事务会失效。
事务的传播属性在上面已经介绍了,默认的事务传播属性是Propagation.REQUIRED
,可是一旦配置了错误的传播属性,也是会致使事务失效,以下三种配置将会致使事务失效:
Propagation.SUPPORTS
Propagation.NOT_SUPPORTED
Propagation.NEVER
在原始的SSM项目中都配置了context:component-scan
而且同时扫描了service层,此时事务将会失效。
按照Spring配置文件的加载顺序来讲,会先加载Springmvc的配置文件,若是在加载Springmvc配置文件的时候把service也加载了,可是此时事务还没加载,将会致使事务没法成功生效。
解决方法很简单,把扫描service层的配置设置在Spring配置文件或者其余配置文件中便可。
事务失效的缘由不少,可是千万不要作到只知其一;不知其二,只有深刻理解了,才能在面试过程当中对答如流。
今天的文章就到此结束了,若是以为陈某写得不错,有所收获的,关注在看来一波,大家的鼓励,将会是我写做的动力,谢谢支持!!!