事务管理对于服务来说是至关重要的,它可以保证用户每一次操作都是可靠的,即便出现了异常的访问,也不至于破坏后台数据的完整性。就想银行的ATM
机一样通常情况下可以为用户提供正常的服务,但是也难免遇到操作过程中突然发生故障的情况,此时,事务就必须确保出故障前对账户的操作不生效,就像用户刚才完全没有使用过取款机一样,以保证用户和银行的利益都不熟损失。
在Spring
中事务是通过TransactionDefinition
接口来定义的。该接口包含与事务属性有关的方法。
public interface TransactionDefinition{
int getIsolationLevel();
int getPropagationBehavior();
int getTimeout();
boolean isReadOnly();
}
Spring
进行事务操作的时候,通过调用以上接口提供的方法必须能够返回事务相关的属性取值。
Spring中的事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition
接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这个值就是TransactionDefinition.ISOLATION_READ_COMMITTED。而MySQL的Innodb的默认值是TransactionDefinition.ISOLATION_REPEATABLE_READ。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取到另一个事务发生修改但没有提交的值,该隔离级别的隔离读最低,会发生脏读,不可重复读和幻读。因此平常很少会使用它
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别是Oracle和SQL Server的默认隔离级别,表示一个事务可以读取到另一个事务发生修改后提交的值,该隔离级别可以防止脏读的发生,但是会昌县不可重复读、和幻读这个值也是大多数情况下推荐的值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别是MySQL的INNODB引擎的默认隔离级别,表示一个事务在整个过程中可以多次重复执行某个查询,并且每值返回的记录都是相同的。即使多次查询之间有新增的数据满足该查询条件,这些新增的记录也会被忽略。该隔离级别可以防止脏读,不可重复读但是会发生幻读的问题。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
Spring中事务的传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition
定义中包括了如下几个表示传播行为的常量:
-
TransactionDefinition.PROPAGATION_REQUIRED
表示需要事务处理,有则使用,无则新建事务,这是
Spring
默认的事务传播级别。该传播级别的特性是,如果Context
中已经存在事务,那么就将当前使用事务的代码加入到Context
的事务中执行,如果当前Context
中不存在事务,就新建一个事务执行代码。这个级别通常能满足大多数的业务场景。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 -
TransactionDefinition.PROPAGATION_SUPPORTS
支持事务,该级别的特性是,如果
Context
存在事务,则将代码加入到Context
的事务中执行,如果Context
中没有事务,则使用非事务的方式执行。如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 -
TransactionDefinition.PROPAGATION_MANDATORY
强制性要求事务。该级别的特性是,当要以事务的方式执行代码时,要求
Context
中必须已经存在事务,否则会抛出异常!使用MANDATORY
强制事务,可以有效地控制“必须以事务的方式执行代码”如果网址给它加上事务控制“这种情况的发生。举一个简单的例子:有一个方法,对这个方法的要求时一旦被调用,该方法就必须包含在事务中才能正常的执行,那么这个方法就适合设置为PROPAGATION_MANDATORY
强制事务传播行为,从而在代码层面加以控制。如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 -
TransactionDefinition.PROPAGATION_REQUIRES_NEW
每次都新建一个事务。该级别的特点是,当执行到一段需要事务的代码时,先判断
Context
中是否已经有事务存在,如果不存在,就新建一个事务;如果已经存在,就suspend
挂起当前事务,然后创建一个新的事务去执行,直到新的事务执行完毕,才会恢复先前挂起的Context
事务。创建一个新的事务,如果当前存在事务,则把当前事务挂起。 -
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
不支持事务。该级别的特点是,如果发现当前
Context
中有事务存在,则挂起该事务,然后以非事务的方式执行逻辑代码,执行完毕后,恢复先前挂起的Context
事务。这种事务的传播行为,可以缩小事务处理过程中的范围。举一个例子,在一个事务中,需要调用一段非核心业务的逻辑操作1000
次,如果将这段逻辑放在事务中,会导致该事务的范围变大,生命周期变长,为了避免因事务范围扩大、周期变长而引发一些事先没有考虚到的异常情况发生比如事务的超时,可以将这段逻辑设置为NOT_SUPPORTED
不支持事务行为。以非事务方式运行,如果当前存在事务,则把当前事务挂起。 -
TransactionDefinition.PROPAGATION_NEVER
对事务要求更严格,不能出现事务!该级别的特点是,设置了该级别的代码,在执行前一旦发现
Context
中有事务存在,就会抛出Runtime
异常,强制停止执行,有我无他。以非事务方式运行,如果当前存在事务,则抛出异常。 -
TransactionDefinition.PROPAGATION_NESTED
嵌套事务。该级别的特点是如果
Context
中存在事务A
,就将当前代码对应的事务B加入到事务A
内部,嵌套执行;如果Context
中不存在事务,则新建事务执行代码。换句话说,事务A
与事务B
之间是父子关系,A
是父,B
是子。理解嵌套事务的关键点是:Save Point
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。- 父、子事务嵌套,
Save Point
的说明- 父事务会在子事务进入之前创建一个
Save Point
- 子事务
RollBack
,父事务只会回滚到Save Point
,而不会回滚整个父事务 - 父事务发起
Commit
后引发子事务的Commit
- 父事务会在子事务进入之前创建一个
这里需要指出的是,前面的六种事务传播行为是
Spring
从EJB
中引入的,他们共享相同的概念。而PROPAGATION_NESTED
是Spring
所特有的。以PROPAGATION_NESTED
启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉JDBC
中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。 - 父、子事务嵌套,
class ServiceA {
void methodA(){
ServiceB.methodB();
}
}
class SerivceB {
void methodB(){
}
}
-
若
ServiceB.methodB()
的传播行为定义为PROPAGATION_REQUIRED
, 那么在执行ServiceA.methodA()
的时候,若ServiceA.methodA()
已经开启了事务,这时调用ServiceB.methodB()
,ServiceB.methodB()
将会运行在ServiceA.methodA()
的事务内部,而不再开启新的事务。而假如ServiceA.methodA()
运行的时候发现自己没有在事务中,就会为它分配一个新事务。这样,在ServiceA.methodA()
或者在ServiceB.methodB()
内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB()
的事务已经被
提交,但是ServiceA.methodA()
在接下来的过程中 fail 要回滚,ServiceB.methodB()
也会跟着一起回滚。 -
假如
ServiceA.methodA()
的传播行为设置为PROPAGATION_REQUIRED
,ServiceB.methodB()
的传播行为为PROPAGATION_REQUIRES_NEW
,那么当执行到ServiceB.methodB()
的时候,ServiceA.methodA()
所在的事务就会挂起,而ServiceB.methodB()
会起一个新的事务,等待ServiceB.methodB()
的事务完成以后,A的事务才会继续执行。PROPAGATION_REQUIRED
与PROPAGATION_REQUIRES_NEW
的事务区别在于事务的回滚程度。因为ServiceB.methodB
是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.methodB
已经提交,那么ServiceA.methodA
失败回滚,ServiceB.methodB
是不会回滚的。如果ServiceB.methodB
失败回滚,如果它抛出的异常被ServiceA.methodA
捕获,ServiceA.methodA
事务仍然可能会提交 -
假如
ServiceA.methodA
的事务传播行为是PROPAGATION_REQUIRED
,而ServiceB.methodB
的事务传播行为是PROPAGATION_NOT_SUPPORTED
,那么当执行到ServiceB.methodB
时,ServiceA.methodA
的事务挂起,而ServiceB.methodB
以非事务的状态运行完之后,再继续ServiceA.methodA
的事务。 -
假如
ServiceA.methodA
的事务传播行为是PROPAGATION_REQUIRED
, 而ServiceB.methodB
的事务级别是PROPAGATION_NEVER
,那么ServiceB.methodB
执行时就会抛出异常。