Spring事务的传播机制
什么是事务传播机制
事务传播机制是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何去执行,例如method1方法添加了事务,去调用了method2事务方法,那么method2是继续在method1的事务中执行,还是新开一个事务时执行,这就需要method2的事务传播机制来决定。
七种事务传播行为
REQUIRED(默认的传播机制)
如果当前没有事务,则自己新建一个事务;如果当前存在事务,则加入这个事务。
场景1
1 | (propagation = Propagation.REQUIRED) |
functionA方法执行时没有事务,于是functionA新建了一个事务,在functionA中调用functionB时,由于已经有一个事务,那么functionB就加入到functionA创建的事务中,现在这两个方法在同一个事务中。当functionB中抛出一个异常之后,事务会发生回滚,即functionA和functionB中的数据库操作都会回滚,因此数据库仍然保持初始状态,没有数据被插入。
场景2
1 | public void functionA() |
与场景1相比,这里functionA没有设置事务传播行为,在functionA中调用functionB时,由于没有事务,那么functionB就创建一个事务,当functionB方法中发生异常,只有functionB中的操作被回滚,functioinA中的操作由于不在事务中,因此不会回滚。
REQUIRES_NEW
创建一个新事务,如果存在当前事务,则挂起该事务。
也就是说,不论当前是否存在事务,当执行一个被注解为Propagation.REQUIRES_NEW
的方法时,都会为这个方法开启一个新的事务。
场景1
1 | (propagation = Propagation.REQUIRED) |
functionA方法执行时存在事务,在functionA中调用functionB时,会为functionB新创建一个事务,这样即使在functionB的事务中,因为出现了异常导致了functoinB发生回滚,但是functonA还是可以正常插入成功。
场景2
1 | (propagation = Propagation.REQUIRED) |
functionA方法执行时存在事务,在functionA中调用functionB时,会为functionB新创建一个事务,这样即使是在functionA方法的事务中发生了异常,也不会影响到functionB,因为functionB是创建的一个新的事务。
NESTED
如果当前事务存在,则在嵌套事务中执行,否则开启一个事务(和REQUIRED的操作一样)。
场景1
1 | (propagation = Propagation.REQUIRED) |
functionA方法执行时存在事务,在functionA中调用functionB时,会为functionB创建一个子事务,即嵌套在functionA的事务中的事务。这时,functioinB正常执行完后回到functionA,在functionA出现了异常,即在父事务中出现异常之后发生了回滚,导致子事务也发生了回滚。
场景2
1 | (propagation = Propagation.REQUIRED) |
functionA方法执行时存在事务,在functionA中调用functionB时,会为functionB创建一个子事务,即嵌套在functionA的事务中的事务。这时,虽然functioinB抛出异常但是被functionA成功捕捉到,因此functionA可以正常执行完 。
同样是这个例子, 如果将functionB的事务传播行为改为REQUIRED,那么会导致1, 11, 2, 3都不会插入成功,因为就算functionA会捕获异常,但是因为functionA和functionB是在同一个事务中,functionB中的异常还是会立即触发整个事务回滚。
SUPPORTS
如果当前存在事务,则加入当前事务;如果当前没有事务,就以非事务方法执行。
场景1
1 | (propagation = Propagation.REQUIRED) |
functionA方法执行时存在事务,在functionA中调用functionB时,就会加入到functionA的事务中,也就是说functionA和functionB现在在一个事务中,因此functionB方法因为抛出了异常,事务会发生回滚,导致这个事务中的所有操作都被取消,一个值也不会插入。在这个场景中,functionB设置的传播行为是SUPPORTS和REQUIRED是一样的效果,都是让两个方法处在同一个事务中。
场景2
1 | public void functionA() |
functionA方法执行时没有事务,在functionA中调用functionB时,不会创建事务,functionB就会以非事务的方式运行,也就是说现在functionA和functionB都是没有事务的,所以1可以成功插入,然后functionB发生了异常,但是并不会回滚,于是2可以成功插入,但因为保存2之后,出现了异常中断了后续程序的执行,因此3不会被插入。
NOT_SUPPORTED
始终以非事务方式执行,如果当前存在事务,则挂起当前事务。
声明为NOT_SUPPORTED
的方法,在执行时,不论当前是否存在事务,都会以非事务的方式执行。
场景1
1 | public void functionA() |
functionA方法执行时没有事务,在functionA中调用functionB时,functionB也不会开启事务,所以functionB中的2可以成功插入,然后发生了异常,因此functionB被中断,3没有插入,这时作为调用方的functionA捕获到了异常,但是因为functionA没有事务,并不会回滚操作,最终不会影到1被插入。
场景2
1 | (propagation = Propagation.REQUIRED) |
functionA方法执行时存在事务,在functionA中调用functionB时,functionB不会开启事务,所以functionB中的2可以成功插入,然后发生了异常,因此functionB被中断,3没有插入,这时作为调用方的functionA捕获到了异常,于是回滚操作,最终导致1也没有插入成功。
MANDATORY
如果当前存在事务,则加入当前事务;如果当前事务不存在,则抛出异常。
场景1
1 | (propagation = Propagation.REQUIRED) |
functionA方法执行时存在事务,在functionA中调用functionB时,就会加入到functionA的事务中,也就是说functionA和functionB现在在一个事务中,因此functionB方法因为抛出了异常,事务会发生回滚,导致这个事务中的所有操作都被取消,一个值也不会插入。在这个场景中,functionB设置的传播行为是MANDATORY和REQUIRED是一样的效果,都是让两个方法处在同一个事务中。
场景2
1 | public void functionA() |
functionA在执行时没有事务,去执行functionB时,就会直接抛出必须要有事务的异常。
NEVER
不使用事务,如果当前事务存在,则抛出异常。
就是我这个方法不使用事务,并且调用我的方法也不允许有事务,如果调用我的方法有事务则我直接抛出异常。
场景1
1 | public void functionA() |
functionA方法执行时没有事务,所以1可以插入。在functionA中调用functionB时,functionB方法也不会开启事务,所以2也可以插入,然后发生了异常,因此functionB被中断,3没有插入。
场景2
1 | (propagation = Propagation.REQUIRED) |
functionA方法执行时存在事务,在functionA中调用functionB时,因为functionB的事务传播方式为NEVER,因此functionB不会执行而是直接抛出异常,functionA会检测到functionB抛出的异常,于是functionA发生回滚,所以1、2、3都不会插入。
生效条件
Spring中事务的默认实现使用的是AOP,也就是代理的方式,编码时同一个Service类中的方法相互调用需要使用注入的对象来调用,不要直接使用this.方法名
来调用,this.方法名
调用是对象内部方法调用,不会通过Spring代理,也就是事务不会起作用。
如何记忆
在Spring中对于事务的传播行为定义了七种类型分别是:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。如果试图硬记下这七种事务传播机制,其实是有难度的,而且也未必能记很久,最好的办法是理解它们,从它们的名字或行为进行归并,这样会好记一些。
- 第一组:REQUIRED, REQUIRED_NEW, NESTED
- 第二组:SUPPORTS, NOT_SUPPORTED
- 第三组:MANDATORY, NEVER
NESTED和REQUIRED的区别:
- REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一事务,那么被调用方出现异常时,由于共用一个事务,所以无论调用方是否catch其异常,事务都会回滚。
- 而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响。
NESTED和REQUIRES_NEW的区别:
- REQUIRES_NEW是新建一个事务并且新开启的这个事务与原有事务无关,而NESTED则是当前存在事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务)。
- 在NESTED情况下父事务回滚时,子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务。