zl程序教程

您现在的位置是:首页 >  后端

当前栏目

事务的传播行为 隔离级别 异常回滚策略

异常事务 策略 行为 级别 隔离 回滚 传播
2023-06-13 09:13:57 时间

事务传播行为

事务的传播行为;propagation:当前方法的事务[是否要和别人公用一个事务]如何传播下去(里面的方法如果用事务,是否和他公用一个事务)

Propagation propagation() default Propagation.REQUIRED;

  REQUIRED:(必须):
      Support a current transaction, create a new one if none exists.
      如果以前有事务,就和之前的事务公用一个事务,没有就创建一个事务;
  REQUIRES_NEW(总是用新的事务):
       Create a new transaction, and suspend the current transaction if one exists.
      创建一个新的事务,如果以前有事务,暂停前面的事务。

  SUPPORTS(支持):
      Support a current transaction, execute non-transactionally if none exists.
      之前有事务,就以事务的方式运行,没有事务也可以;

  MANDATORY(强制):没事务就报错
      Support a current transaction, throw an exception if none exists
      一定要有事务,如果没事务就报错


  NOT_SUPPORTED(不支持):
      Execute non-transactionally, suspend the current transaction if one exists
      不支持在事务内运行,如果已经有事务了,就挂起当前存在的事务

  NEVER(从不使用):
       Execute non-transactionally, throw an exception if a transaction exists.
       不支持在事务内运行,如果已经有事务了,抛异常


  NESTED:
      Execute within a nested transaction if a current transaction exists,
      开启一个子事务(MySQL不支持),需要支持还原点功能的数据库才行;

举例子: 一家人带着老王去旅游;

  一家人:开自己的车还是坐老王的车

  Required:坐老王车
  Requires_new:一定得开车,开新的

  SUPPORTS:用车,有车就用,没车走路;
  MANDATORY:用车,没车就骂街。。。

  NOT_SUPPORTED:不支持用车。有车放那不用
  NEVER:从不用车,有车抛异常

其实平常用到的也就Required Requires_new

例子分析:

Required_new
 外事务{

     A();Required; A
     B();Requires_new B
     //try{
     C();Required; C
     //}catch(Exception e){
         //c出异常?
     //}

     D();Requires_new; D

     //给数据库存 --外事务的(就是本方法的)

    // int i = 10/0;

 }
  • 场景1: A方法出现了异常;由于异常机制导致代码停止,下面无法执行,数据库什么都没有
  • 场景2: C方法出现异常;A回滚,B成功,C回滚,D无法执行,外无法执行
  • 场景3: 外成了后,int i = 10/0; B,D成功。A,C,外都执行了但是必须回滚
  • 场景4: D炸;抛异常。外事务感知到异常。A,C回滚,外执行不到,D自己回滚,B成功
  • 场景5: C如果用try-catch执行;C出了异常回滚,由于异常被捕获,外事务没有感知异常。A,B,D都成,C自己回滚

总结: 对这段代码而言 传播行为过程中,只要Requires_new被执行过就一定成功,不管后面出不出问题。异常机制还是一样的,出现异常代码以后不执行。 Required只要感觉到异常就一定回滚。和外事务是什么传播行为无关。

传播行为总是来定义,当一个事务存在的时候,他内部的事务该怎么执行。

实例用法: 下面这个方法里的每一个save方法都用重新开启一个事务的

@Transactional(propagation = Propagation.REQUIRES_NEW)
	@Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void saveProduct(PmsProductParam productParam) {
        ProductServiceImpl proxy = (ProductServiceImpl) AopContext.currentProxy();
        //1)、pms_product:保存商品基本信息
        proxy.saveBaseInfo(productParam);

        //5)、pms_sku_stock:sku_库存表
        proxy.saveSkuStock(productParam);

        /**
         * 2如果炸了 3 4 是执行不到的 如果想互不影响 可以每个try-catch来互不影响
         */
        //2)、pms_product_attribute_value:保存这个商品对应的所有属性的值
        proxy.saveProductAttributeValue(productParam);

        //3)、pms_product_full_reduction:保存商品的满减信息
        proxy.saveFullReduction(productParam);

        //4)、pms_product_ladder:满减表
        proxy.saveProductLadder(productParam);

    }

那么这段代码为什么要用AopContext.currentProxy()获取当前代理对象呢? spring的事务是aop动态代理做的 我们想用事务 必须获取代理对象来调用方法 通过 对象.方法()才能加上事务。 例如:

A{
      A(){
          B(); //1,2,3
          C(); //4,5,6
          D(); //7,8,9
      }
  }

A类调用自身的B C D 三个方法 其实就是这样

 A(){
      //1,2,3,4,5,6,7,8,9
      //
  }

自己类调用自己类里面的方法,就是一个复制粘贴。归根到底,只是给 controller{ serviceProxy.a(); } 只有一个事务 而我们使用

  A{
      A(){
          hahaService.B();
          hahaService.C();
          hahaService.D();

      }
  }

是可以在A类的A方法内部开启事务的。

事务的问题: Service自己调用自己的方法,无法加上真正的自己内部调整的各个事务 因此我们这样解决: 要是能拿到ioc容器,从容器中再把我们的组件获取一下,用对象调方法。

事务的最终解决方案;

普通加事务。导入jdbc-starter,@EnableTransactionManagement,加@Transactional

方法自己调自己类里面的加不上事务。

  1. 导入aop包,开启代理对象的相关功能
     <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
     </dependency>
  1. 获取到当前类真正的代理对象,去调方法即可 1)、@EnableAspectJAutoProxy(exposeProxy = true):暴露代理对象 2)、获取代理对象;

隔离级别

隔离级别:通过解决读写加锁问题的(数据底层的方案)。 mysql默认可重复读(快照);

读未提交:改数据时候不加锁 别人可以读 读已提交:改数据时候加锁 数据改完才能读 可重复读:只要这个线程没释放完 读的都是之前的数据 串行化:

异常回滚策略

异常回滚策略 异常: 运行时异常(不受检查异常) ArithmeticException … 编译时异常(受检异常) FileNotFound;编译时异常要么throw要么try- catch

事务运行的异常默认是一定回滚 编译时异常默认是不回滚的; 可以通过rollbackFor:指定哪些异常一定回滚的。