@Transactional介绍
@Transactional
是声明式事务的注解,可以被标记在类上
、接口
、方法
上。- 该注解中有很多值得深入了解的几种属性,我们来看一下。
transactionManager
- 指定事务管理器,值为
bean
的名称,这个主要用于多事务管理器情况下指定。比如多数据源配置的情况下。isolation
- 事务的隔离级别,默认是
Isolation.DEFAULT
。- 几种值的含义如下:
Isolation.DEFAULT
:事务默认的隔离级别,使用数据库默认的隔离级别。Isolation.READ_UNCOMMITTED
:这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。Isolation.READ_COMMITTED
:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻读。Isolation.REPEATABLE_READ
:这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读。Isolation.SERIALIZABLE
:这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。propagation
- 代表事务的传播行为,默认值为
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
点,也就是在进入子事务之前,父事务建立一个回滚点,与当前事务同步提交或回滚。 子事务是父事务的一部分,在父事务还未提交时,子事务一定没有提交。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。timeout
- 事务的超时时间,单位为秒。
readOnly
- 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。如果一个事务只涉及到只读,可以设置为true。
rollbackFor 属性
- 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
- 默认是在
RuntimeException
和Error
上回滚。noRollbackFor
- 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
问题1-方法中调用同类的方法
问题概述
- 简单的说就是一个类中的
A方法
(未标注声明式事务)在内部调用了B方法
(标注了声明式事务),这样会导致B方法中的事务失效。 - 代码如下:
1 | public class Test{ |
- 为什么会失效呢?:其实原因很简单,Spring在扫描Bean的时候会自动为标注了
@Transactional
注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B()
,此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效。
解决方案
知道原因之后就好解决了,想办法获取该类的代理类,通过代理类来调用B方法就行
1 | public void A(){ |
加入后运行会报个错,需要你设置exposeProxy = true
在该类上加个注解
1 | //暴露代理对象 |
此时就能正常回滚
问题2-异常被捕获了
问题概述
在整个事务的方法中使用try-catch
,导致异常无法抛出,自然会导致事务失效。伪代码如下:
1 |
|
问题3-在非public修饰的方法使用
问题概述
@Transactional注解使用的是AOP,在使用动态代理的时候只能针对public
方法进行代理,源码依据在AbstractFallbackTransactionAttributeSource
类中的computeTransactionAttribute
方法中,如下:
1 | protected TransactionAttribute computeTransactionAttribute(Method method, |
问题4-propagation属性设置错误
问题概述
事务的传播属性在上面已经介绍了,默认的事务传播属性是Propagation.REQUIRED
,但是一旦配置了错误的传播属性,也是会导致事务失效,如下三种配置将会导致事务失效:
- Propagation.SUPPORTS
- Propagation.NOT_SUPPORTED
- Propagation.NEVER
propagation
- 代表事务的传播行为,默认值为
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
点,也就是在进入子事务之前,父事务建立一个回滚点,与当前事务同步提交或回滚。 子事务是父事务的一部分,在父事务还未提交时,子事务一定没有提交。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
问题5-rollbackFor属性或noRollbackFor属性设置错误
问题概述
- rollbackFor很容易理解,指定异常触发回滚,一旦设置错误,导致一些异常不能触发回滚,此时的声明式事务不就失效了吗。
- 这个和rollbackFor属性设置错误类似,一旦设置错误,也会导致异常不能触发回滚,此时的声明式事务会失效。
问题6-底层数据库引擎不支持事务
问题概述
如果数据库引擎不支持事务,则Spring自然无法支持事务。
问题7-本地事务在分布式情况下的问题
问题概述
假设服务A一个添加了@Transactional注解的服务类中存在两个远程调用服务B和C(先调用B,后调用C)的方法(该方法本身也标记了@Transactional注解,也是事务)
- B运行成功但是由于网络故障等原因没有返回,导致A认为B运行失败,则A回滚但是B已经运行成功不会回滚,造成数据不一致
- B运行成功且成功返回,但是在调用C的时候出现问题,这时C因为本身的事务性回滚,A中因为C出现问题导致远程调用失败也回滚,但是B由于是远程调用是不会回滚的,导致出现数据不一致问题
解决方案
使用分布式事务的解决方案,例如可以使用基于2pc的seata。