Spring Boot2:整合事务

什么是事务?

我们在开发企业应用时,通常业务人员的一个操作实际上是对数据库读写的多步操作的结合。由于数据库在顺序执行的过程中,任何一步都有可能发生异常,异常会导致后续操作无法完成,此时由于业务逻辑并未正确的完成,之前的操作的数据并不可靠,如果要让这个业务正确的执行下去,通常有实现的方式:

1.记录失败的位置,问题修复之后,从上一次执行失败的位置开始继续执行后面要做的业务逻辑。

2.在执行失败的时候,回退本次执行的所有过程,让操作恢复到原始状态,待问题修复之后,重新执行原来的业务逻辑

事务就是针对上诉方式2的实现。事务,一般是指要做的或所做的事情,就是上面所说业务人员的一个操作(比如电商系统中,一个创建订单的操作包含了创建订单、商品库存的扣减两个基本的操作。如果创建订单成功,库存扣减失败,那么就会出现商品超卖的问题,所以最基本的就是需要为这两个操作用事务包括起来,保证这两个操作要么都成功,要么都失败)。

未开启事务

无事务运行,保留出错前的数据,不涉及回滚。

@Test
public void contextLoads(){
    int money2 = 100;
    //jerry
    Useru useru3  = userDao.selectUserById(1);
    //tom
    Useru useru4  = userDao.selectUserById(2);
    //jerry - money
    userService.updUser(
            new Useru(useru3.getId(),useru3.getName(),useru3.getMoney()-money2));
    System.out.println(useru3.getName() + " - "+ money2);
    /*制造异常*/
    int result = 1/0; 
    //tom + money
    userService.updUser(
            new Useru(useru4.getId(),useru4.getName(),useru4.getMoney()+ money2));
    System.out.println(useru4.getName() + " - "+ money2);
}

未开启事务

开启事务

在头部加上

@Transactional

1. 无嵌套的正常事务

public class C{
    @Transactional
    public void a(){
         //TODO
    }
}

【结论】:出现异常,正常回滚。

原因:

事务的 ACID 特性中的 C,Consistency 一致性,事务内有操作失败时则数据将全部回滚到修改前的状态。

无嵌套的正常事务

2. 方法A调用带事务的方法B

public class C{
    public void a(){
        b();
    }
    @Transactional
    public void b(){
         //TODO
    }
}

【结论】:同一个类内,方法 A 调用了带事务的方法 B,则方法 B 的事务将失效。

原因:

spring aop 动态代理机制给当前类 C 生成代理类 PolicyC,然后 A 调用 B 还是在 C 类中调用(而不是代理类 PolicyC),代理类 PolicyC 中的 B 方法并没有被调用到。

方法A调用带事务的方法B

解决方案:

只有在不同的类 C 和 C1 中,C 类中的 A 方法调用 C1 类中的 B 方法,这样 B 中的事务就会生效。

public class C{
   @Autowired 
   private C1 c1;
   //注入 C1 类调用 c1 类中的 B() 方法
   public void A(){
      c1.B();
   }
}
public class C1{
   @Transactional
   public void B(){
       //TODO
   }
}

3. 加入异常捕获处理的事务

public class C{
    @Transactional
    public void a(){
        try {
            //TODO
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

【结论】:事务方法中对原子操作进行普通的 try-catch 时,事务会失效。

原因:

默认spring事务只在发生未被捕获的 RuntimeExcetpion 时才回滚。

Spring 声明式事务默认会对非检查型异常和运行时异常进行回滚,而对检查型异常不进行回滚操作。Error和RuntimeException及其子类为非检查型异常。通过如下几种方式改变其规则:

1)让 checked 异常也进行回滚,使用 @Transactional(rollbackFor = Exception.class)

2)让 unchecked 异常也不回滚,使用 @Transactional(notRollbackFor = RunTimeException.class)

3)不需要事务,@Transactional(propagation = Propagation.NOT_SUPPORTED)

4)如果不添加 rollbackFor 等属性,Spring碰到 Unchecked Exceptions 都会回滚,不仅是RuntimeException,也包括 Error。

因此,本场景下会让 spring aop 无法正常捕捉到运行时的事务中的异常,从而事务失效。

加入异常捕获处理的事务

解决方案:

  1. 在 catch 语句中,继续抛出 Exception。不建议这么做,不太友好(调用者需捕获处理异常)。
 public class C{
     @Transactional
     public void a() throws Exception {
         try {
             //TODO
         } catch (Exception e) {
             e.printStackTrace();
             throw new Exception(); //或用 throw new RuntimeException() 则无需往上继续抛,可被 aop 捕获进而事务回滚
         }
     }
 }
  1. 对于要进行事务的方法,不使用 try catch,在上层调用的时候处理异常;
    说明:同样不能再同一个类内,原因参考 方法A调用带事务的方法B 场景。

  2. 在 catch 语句中增加 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常。

 public class C{
     @Transactional
     public void a() {
         try {
             //TODO
         } catch (Exception e) {
             e.printStackTrace();
             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //手动回滚
         }
     }
 }

4. 事务回滚的注意事项

  1. 要想事务起作用,必须是主方法名上有 @Transactional 注解,方法体内不能用 try catch;如果用 try catch,则 catch 中必须用 throw new RuntimeException();
  2. @Transactional 注解应该只被应用到 public 方法上,不要用在 protected、private 等方法上,即使用了也将被忽略,不起作用,这是由 Spring AOP 决定的。
  3. 只有来自外部的方法调用才会被 AOP 代理捕捉,类内部方法调用类内部的其他方法,子方法并会不引起事务行为,即使被调用的方法上使用有 @Transactional 注解。
  4. 类内部方法调用内部的其他方法,被调用的方法体中如果有 try catch,则 catch 中必须用 throw new RuntimeException(),否则即使主方法上加上 @Transactional 注解,如果被调用的子方法出错也不会抛出异常,不会引起事务起作用。

5. 事务传播行为分析

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务的执行行为。

七大事务传播行为:

传播行为含义
Propagation.REQUIRED如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个事务执行(默认的事务传播行为)
Propagation.SUPPORTS如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
Propagation.MANDATORY如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
Propagation.REQUIRES_NEW创建一个新的事务,如果当前存在事务,则把当前事务挂起
Propagation.NOT_SUPPORTED以非事务方式运行,如果当前存在事务,则把当前事务挂起
Propagation.NEVER以非事务方式运行,如果当前存在事务,则抛出异常(与 MANDATORY 相反)
Propagation.NESTED如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,可独立与外围事务进行单独提交或者回滚。;如果当前没有事务,则该取值等价于 REQUIRED
5.0 无事务

无事务运行,保留出错前的数据,不涉及回滚。

无事务

5.1 REQUIRED 示例解析

没有事务则创建一个事务执行(默认的事务传播行为)。
@Transactional 等价于 @Transactional(propagation = Propagation.REQUIRED)
REQUIRED 示例解析

5.2 REQUIRES_NEW 示例解析

场景①:新起的事务抛出异常会不会让外围事务回滚?会!

REQUIRES_NEW 示例解析1

场景②:外围事务失败会不会导致新起的事务中已提交的数据进行回滚?会!

REQUIRES_NEW 示例解析2

5.3 SUPPORTS 示例解析

场景①:调用者不存在事务,则不会回滚。

SUPPORTS 示例解析1

场景②:调用者存在事务,则会回滚。

SUPPORTS 示例解析2

5.4 NOT_SUPPORTED 示例解析

当前不支持事务。比如 ServiceA.methodA 的事务级别是 PROPAGATION_REQUIRED ,而 ServiceB.methodB 的事务级别是 PROPAGATION_NOT_SUPPORTED ,那么当执行到 ServiceB.methodB 时,ServiceA.methodA 的事务挂起,而他以非事务的状态运行完,再继续 ServiceA.methodA 的事务。

NOT_SUPPORTED 示例解析1

NOT_SUPPORTED 示例解析2

如果是同一个类内,该事务级别不会生效。

即 A 类的 a 方法使用事务 REQUIRED,调用 A 类内部的 b 方法使用事务 NOT_SUPPORTED 时,不论 a或b 谁出现异常,则都会回滚。(已验证)

5.5 MANDATORY 示例解析

必须在事务中运行,否则就会抛出异常。

MANDATORY 示例解析1

MANDATORY 示例解析2

5.6 NEVER 示例解析

不能在事务中使用,否则抛出异常。而且在非事务中使用时,一旦出现异常也不会进行回滚。

NEVER 示例解析1

NEVER 示例解析2

5.7 NESTED 示例解析

嵌套事务,外围如果没有事务则自己另起一个事务,可独立与外围事务进行自身事务的单独提交或者回滚

NESTED 示例解析

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值