spring中的@Transactional声明式事务

发布时间 2023-07-09 21:28:10作者: 想去放牛
 

1 与编程式事务区别

1.1 声明式事务

使用@Transactional注解来实现事务创建的,spring会为加了事务配置的类创建一个代理对象,基于动态代理,通过其中参数来控制事务的传播、事务回滚等。加在类上相当于给类中所有方法都添加事务。使用声明式事务的好处是使用简单,减少很多像是开启注解、提交、回滚的这些冗余代码,但是使用中有些事务失效的场景需要注意。

1.2 编程式事务

相比于声明式事务,编程式事务需要手动的控制事务的开启、提交、回滚。编程式事务有着明确的事务边界,更方便代码阅读与调试。手动控制事务操作需要严密,未提交会造成数据死锁问题。

 

2 事务传播行为

合理的运用事务传播行为,可以减少不必要的事务开销。

 

1.PROPAGATION_REQUIRED

这个是Spring默认的事务传播行为,它指的是如果外层调用方法已经开启了事务,那么当前方法就加入到外层事务。如果外层调用方没有开启事务,那么当前方法就开启一个事务。这种行为可以保证多个嵌套的事务方法在同一个事务内执行,可以保证多个事务同时提交或者同时回滚。这个机制可以满足大多数业务场景。

系统中基本都使用的这个。

2.PROPAGATION_REQUIR_NEW

这个传播行为是每次都开启一个事务。如果外层调用方法已经开启了事务,就先把外层事务挂起,然后执行当前新事务,执行完毕后再恢复上层事务的执行。这种行为如果内层方法抛出了Exception异常会回滚当前事务,但是不会影响外层方法的执行。

当内层事务需要进行数据隔离、独占式运行时使用。

3. PROPAGATION_SUPPORT

这个传播行为是指,如果外层方法开启了事务,那么当前方法加入到外层事务,如果外层方法没有开启事务,那么当前方法也不会创建事务,直接使用非事务方式执行。

4. PROPAGATION_NOT_SUPPORT

这个传播行为不支持事务,也就是说如果外层方法开启了事务,就挂起事务,然后以非事务方式执行当前方法,等执行结束后再恢复外层事务的执行。

事务中调用的一些查询、删除就不需要开启事务,减少事务的性能开销。

5.PROPAGATION_NEVER

这个传播行为不支持事务,也就是说如果外层方法开启了事务,就执行当前方法前会抛出异常。

明确哪些方法是不加入事务的,减少没必要的事务开销。

6.PROPAGATION_MANDATORY

这个传播行为是指配置了这个传播行为的方法只能在已经存在事务的方法中被调用,如果外层调用方法不存在事务,则会抛出异常,外层方法存在事务就加入。

7.PROPAGATION_NESTED

这个传播行为是指当外层调用方法存在事务时,当前方法合并到外层事务,如果外层方法没有开启事务,就当前开启事务。这点和PROPAGATION_REQUIR传播性一致。不同的是,该传播行为可以保存状态保存点,当事务回滚时,可以回滚到某一个保存点上,而不是回滚所有事务。子事务可以独立回滚,也可以通过传递异常,让父事务也回滚,根源在于用户策略,在父事务通过try catch 对子事务进行包裹,灵活策略。

 

3 除了propagation的其他参数

1.  isolation

事务的隔离级别,默认是使用的数据库的。

2.  timeout

事务的超时时间,默认使用的数据的(mysql的8h,超时自动回滚)。

3.  readOnly

是否只读事务,默认是false,如果事务中只有读操作,设置为true会进行事务优化。

4.  rollBackFor和rollBackForClassName

事务回滚异常,默认是发生运行时异常时回滚。

5.  noRollbackFor和norollbackForClassName

指定异常回滚。

 

4 类外调用与类内调用

4.1 类内调用

1.  有事务方法A调用无事务方法B,AB都会加入事务。(Transactional的事务延伸)

2.  无事务A方法调用有事务B方法,属于this调用,内部调用无法被代理对象拦截,B方法事务不会生效。

4.2 类外调用

1.  @Transactional加在类上,外部调用本类时,本类中所有方法都加上了事务。

2.  类1有事务方法A调用类2无事务方法B,AB都被加入A事务。

a.  类2中B再调用类2中无事务方法C,方法A,B,C都加入A事务。

b.  类2中B再调用类2中有事务方法C,方法A,B加入A事务,C根据其事务传播行为判断。

3.  类1无事务方法A调用类2有事务方法B,A无事务,B有事务。

a.  类2中B再调用类2中无事务方法C,C加入到B事务。

b.  类2中B再调用类2中有事务方法C,C根据其事务传播行为来判断。

4.  类1有事务方法A调用类2有事务方B,A中有事务,B中根据B事务的事务传播行为来判断

4.3 两层以上类外调用

1.  类1中无事务方法A调用类2中有事务方法B,类2中B再调用类3中无事务方法C,A中无事务,B中有事务,C加入到B事务中。

2.  类1中无事务方法A调用类2中有事务方法B,类2中B再调用类3中有事务方法C,A中无事务,B中有事务,C根据其事务传播行为判断。

3.  类1中有事务方法A调用类2中有事务方法B,类2中B再调用类3中无事务方法C,A中有事务。

a.  如果B加入事务A,则C也加入到事务A。

b.  如果B新开一个事务,则C加入到事务B。

4.  类1中有事务方法A调用类2中无事务方法B,类2中B再调用类3中无事务方法C,B和C都加入到A事务中。

5.  类1中有事务方法A调用类2中无事务方法B,类2中B再调用类3中有事务方法C,B加入到A事务中,C根据事务传播行为判断。

6.  类1中有事务方法A调用类2中有事务方法B,类2中B再调用类3中有事务方法C

a.  如果B加入A事务

1.  B加入A事务,C根据其事务传播行为判断。

b.  如果B新开一个事务

1.  A有事务,B有事务,C根据其事务传播行为判断。(A、B在不同的事务)

4.4 注意

1.  根据事务传播行为判断时,主要判断是现有事务还是新开事务。得结合事务传播行为,分析中间方法是否有事务,有事务的话的事务是属于谁,这样无论嵌套多少层事务都可以根据以上分析出结果。

2.  事务不应该过多的延伸,过多的事务嵌套可能导致性能下降、资源竞争、死锁等问题。

 

5 @Transactional的使用注意事项

1.  需要数据库支持事务,@Transactional才会生效,spring声明式事务依赖于数据库的事务。

2.  spring事务只对交由spring管理的对象生效。

3.  自身调用中的问题,同一个类中非事务方法调用加了@Transactional方法@Transactional,不会生效,因为spring声明式事务基于动态代理,调用本类中事务方法属于this调用,不会触发方法拦截器进行代理。

4.  当事务传播类型为NOT_SUPPORTED时,表示不支持事务,事务也不会生效。

5.  在事务方法中捕获了异常catch中做完操作需要抛出异常或是手动提交、回滚事务,默认事务会回滚运行时异常和错误。

异常体系:

6 实际应用中的@Transactional使用实例

6.1 对事务方法中的异常需要捕获,返回指定格式数据或是进行其他操作时

为保证操作的一致性加入事务,其中又使用try...catch捕获执行过程中异常,catch执行发生异常的后续操作、如记录日志等,为了让事务回滚有重新在catch中抛出新的异常。

也可使用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()来手动回滚事务。

手动回滚使用:

 

try{
......
}catch{
//存日志
//其他操作
.....
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
}

 

 

6.2 当一个方法中涉及到操作多张表时

一个方法需要操作多张表时,需要加上事务保证数据的一致性,避免操作过程中发生异常出现脏数据。

6.3 为保证数据一致性,有些查询也需要加入事务

一个查询中涉及到多张表查询的时候,例如在做统计的时候,为保证数据的一致性,也是需要加上事务。

7 其他

为什么事务内层方法要加Transactional?

1.  可以使用不同的事务传播行为:内事务的存在使得我们可以为内层方法选择不同的事务传播行为。例如,可以使用 REQUIRES_NEW 传播行为来创建一个新的事务,当内层方法执行期间外层事务回滚时,内层事务可以继续正常执行。

2.  提高代码的可读性和维护性:通过在内层方法上添加 @Transactional 注解,可以清晰地表达该方法需要独立的事务处理,使得代码的意图更加清晰,更易于理解和维护。