事务管理对于服务来说是至关重要的,它可以保证用户每一次操作都是可靠的,即便出现了异常的访问,也不至于破坏后台数据的完整性。就想银行的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

    这里需要指出的是,前面的六种事务传播行为是 SpringEJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTEDSpring 所特有的。以 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_REQUIREDServiceB.methodB() 的传播行为为 PROPAGATION_REQUIRES_NEW,那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,而 ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,A的事务才会继续执行。PROPAGATION_REQUIREDPROPAGATION_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 执行时就会抛出异常。