关于java:18个示例详解-Spring-事务传播机制附测试源码

24次阅读

共计 9554 个字符,预计需要花费 24 分钟才能阅读完成。

什么是事务流传机制

事务的流传机制,顾名思义就是多个事务办法之间调用,事务如何在这些办法之间流传。

举个例子,办法 A 是一个事务的办法,办法 A 执行的时候调用了办法 B, 此时办法 B 有无事务以及是否须要事务都会对办法 A 和办法 B 产生不同的影响,而这个影响是由两个办法的事务流传机制决定的。

流传属性 Propagation 枚举

Spring 对事务的流传机制在 Propagation 枚举中定义了 7 个分类:

  • REQUIRED 默认
  • SUPPORTS 反对
  • MANDATORY 强制
  • REQUIRES_NEW 新建
  • NOT_SUPPORTED 不反对
  • NEVER 从不
  • NESTED 嵌套

事务的流传机制,是 spring 规定的。因为在开发中,最简略的事务是,业务代码都处于同一个事务下,这也是默认的流传机制,如果呈现的报错,所有的数据回滚。
然而在解决简单的业务逻辑时,办法之间的调用,有以下的需要:

  • 调用的办法须要新增一个事务,新事务和原来的事务各自独立。
  • 调用的办法不反对事务
  • 调用的办法是一个嵌套的事务

7 种流传机制详解

首先创立两个办法 A 和 B 实现数据的插入,插入数据 A:

public class AService {public void A(String name) {userService.insertName("A-" + name);
    }

}

插入数据 B:

public class BService {public void B(String name) {userService.insertName("B-" + name);
    }

}

应用伪代码创立 mainTest 办法和 childTest 办法

   public void mainTest(String name)  {
        // 存入 a1
        A(a1);
        childTest(name);
    }

main 调用 test 办法,其中

   public void childTest(String name)  {
        // 存入 b1
        B(b1);
       throw new RuntimeException(); 
       // 存入 b2
       B2(b2);
    }

以上伪代码,调用 mainTest 办法,如果 mainTest 和 childTest 都不应用事务 的话,数据存储的后果是如何呢?

因为都没应用事务,所以 a1 和 b1 都存到胜利了,而之后抛出异样之后,b2 是不会执行的。所以 a1 和 b1 都插入的数据,而 b2 没有插入数据。

REQUIRED(默认事务)

      /**
     * Support a current transaction, create a new one if none exists.
     * Analogous to EJB transaction attribute of the same name.
     * <p>This is the default setting of a transaction annotation.
     */
    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

如果以后不存在事务,就新建一个事务。如果存在事务,就退出到以后事务。这是一个默认的事务

示例 1 :依据场景举个例子,在 childTest 增加事务,设置流传属性为 REQUIRED,伪代码如下:

   public void mainTest(String name)  {
        // 存入 a1
        A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.REQUIRED)
   public void childTest(String name)  {
        // 存入 b1
        B(b1);
       throw new RuntimeException();}

因为 mainTest 没有事务,而 childTest 又是新建一个事务,所以 a1 增加胜利。在 childTest 因为抛出了异样,不会执行 b2 增加,而 b1 增加回滚。最终 a1 增加胜利,b1 没增加胜利。

示例 2 :在 mainTest 和 childTest 都增加事务,流传属性都为 REQUIRED,伪代码如下:

   @Transactional(propagation = Propagation.REQUIRED)
   public void mainTest(String name) {
        // 存入 a1
        A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.REQUIRED)
   public void childTest(String name) {
        // 存入 b1
        B(b1);
       throw new RuntimeException();}

依据 REQUIRED 流传属性,如果存在事务,就退出到以后事务。两个办法都属于同一个事务,同一个事务的话,如果有产生异样,则全副都回滚。所以 a1 和 b1 都没增加胜利。

SUPPORTS

/**
     * Support a current transaction, execute non-transactionally if none exists.
     * Analogous to EJB transaction attribute of the same name.
     * <p>Note: For transaction managers with transaction synchronization,
     */

如果以后没有事务,则以非事务的形式运行。如果存在事务,就退出到以后事务。

示例 3 :childTest 增加事务,流传属性设置为 SUPPORTS,伪代码如下:

   public void mainTest(String name) {A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.SUPPORTS)
   public void childTest(String name) {B(b1);
       throw new RuntimeException();}

流传属性为 SUPPORTS,如果没有事务,就以非事务的形式运行。表明两个办法都没有应用事务,没有事务的话,a1、b1 都增加胜利。

示例 4 :mainTest 增加事务,设置流传属性为 REQUIRED。childTest 增加事务,设置流传属性为 SUPPORTS,伪代码如下:

   @Transactional(propagation = Propagation.REQUIRED)
   public void mainTest(String name) {A(a1);
        childTest(name);
    }
   @Transactional(propagation = Propagation.SUPPORTS)
   public void childTest(String name) {B(b1);
       throw new RuntimeException(); 
       B2(b2);
    }

SUPPORTS 流传属性,如果存在事务,就退出到以后事务。mainTest 和 childTest 都属于同一个事务,而 childTest 抛出异样,a1 和 b1 增加都回滚,最终 a1、b1 增加失败。

MANDATORY

/**
     * Support a current transaction, throw an exception if none exists.
     * Analogous to EJB transaction attribute of the same name.
     */

如果存在事务,就退出到以后事务。如果不存在事务,就报错
这就阐明如果想调用 MANDATORY 流传属性的办法,肯定要有事务,不然就会报错。

MANDATORY 相似性能限度,必须要被有事务的办法的调用,不然就会报错。

示例 5 :首先在 childTest 增加事务,设置流传属性为 MANDATORY,伪代码如下:

  public void mainTest(String name) {A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.MANDATORY)
  public void childTest(String name) {B(b1);
      throw new RuntimeException(); 
      B2(b2);
   }

在控制台间接报错:

No existing transaction found for transaction marked with propagation 'mandatory'

阐明被标记为 mandatory 流传属性没找到事务,间接报错。因为 mainTest 没有事务,a1 增加胜利。而 childTest 因为报错,b1 增加失败。

示例 6 :mainTest 增加事务,设置流传属性为 REQUIRED。childTest 增加事务,设置流传属性为 MANDATOR,伪代码如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.MANDATORY)
  public void childTest(String name) {B(b1);
      throw new RuntimeException();}

如果存在事务,就把事务退出到以后事务。同一个事务中 childTest 抛出异样,a1 和 b1 增加被回滚,所以 a1 和 b1 增加失败。

REQUIRES_NEW

   /**
     * Create a new transaction, and suspend the current transaction if one exists.
     * Analogous to the EJB transaction attribute of the same name.
     * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
     */

创立一个新的事务。如果存在事务,就将事务挂起。

无论是否有事务,都会创立一个新的事务。

示例 7 :childTest 增加事务,设置流传属性为 REQUIRES_NEW,伪代码如下:

  public void mainTest(String name) {A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {B(b1);
      throw new RuntimeException();}

mainTest 不存在事务,a1 增加胜利,childTest 新建了一个事务,报错,回滚 b1。所以 a1 增加胜利,b1 增加失败。

示例 8 :mainTest 增加事务,设置流传属性为 REQUIRED。childTest 增加事务,设置流传属性为 REQUIRES_NEW,伪代码如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {A(a1);
       childTest(name);
   }
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {B(b1);
      throw new RuntimeException();}

mainTest 创立了一个事务,childTest 新建一个事务,在 childTest 事务中,抛出异样,b1 回滚,异样抛到 mainTest 办法,a1 也回滚,最终 a1 和 b1 都回滚。

示例 9 :在 示例 8 中,如果不想让 REQUIRES_NEW 流传属性影响到被调用事务,将异样捕捉就不会影响到被调用事务。

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {A(a1);
       try {childTest(name);
       } catch (Exception e) {e.printStackTrace();
       }   
   }
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {B(b1);
      throw new RuntimeException();}

childTest 抛出了异样,在 mainTest 捕捉了,对 mainTest 没有影响,所以 b1 被回滚,b1 增加失败,a1 增加胜利。

示例 10:mainTest 设置流传属性为 REQUIRED,并在 mainTest 抛出异样。childTest 同样设置 REQUIRES_NEW 流传属性,伪代码如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {A(a1);
       childTest(name);
       throw new RuntimeException();}
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void childTest(String name) {B(b1);  
       B2(b2);
   }

childTest 是一个新建的事务,只有不抛出异样是不会回滚,所以 b1 增加胜利,而 mainTest 抛出了异样,a1 增加失败。

REQUIRES_NEW 流传属性如果有异样,只会从被调用方影响调用方,而调用方不会影响调用方,即 childTest 抛出异样会影响 mainTest,而 mainTest 抛出异样不会到 childTest。

NOT_SUPPORTED

  /**
     * Execute non-transactionally, suspend the current transaction if one exists.
     * Analogous to EJB transaction attribute of the same name.
     * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
     */

无论是否存在以后事务,都是以非事务的形式运行

示例 11:childTest 增加事务,设置流传属性为 NOT_SUPPORTED,伪代码如下:

  public void mainTest(String name) {A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  public void childTest(String name) {B(b1);  
      throw new RuntimeException();}

NOT_SUPPORTED 都是以非事务的形式执行,childTest 不存在事务,b1 增加胜利。而 mainTest 也是没有事务,a1 也增加胜利。

示例 12:childTest 增加事务,设置流传属性为 NOT_SUPPORTED,mainTest 增加默认流传属性 REQUIRED,伪代码如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NOT_SUPPORTED)
  public void childTest(String name) {B(b1);  
      throw new RuntimeException();}

其中 childTest 都是以非事务的形式执行,b1 增加胜利。而 mainTest 存在事务,报错后回滚,a1 增加失败。

NEVER

   /**
     * Execute non-transactionally, throw an exception if a transaction exists.
     * Analogous to EJB transaction attribute of the same name.
     */

不应用事务,如果存在事务,就抛出异样

NEVER 的办法不应用事务,调用 NEVER 办法如果有事务,就抛出异样。

示例 13:childTest 增加 NEVER 流传属性,伪代码如下:

  public void mainTest(String name) {A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NEVER)
  public void childTest(String name) {B(b1);  
      throw new RuntimeException();  
      B2(b2);
   }

NEVER 不应用事务,mainTest 也不应用事务,所以 a1 和 b1 都增加胜利,b2 增加失败。

示例 14:mainTest 增加 REQUIRED 流传属性,childTest 流传属性设置为 NEVER,伪代码如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NEVER)
  public void childTest(String name) {B(b1);  
      throw new RuntimeException();}

mainTest 存在事务,导致 childTest 报错,b1 增加失败,childTest 抛错到 mainTest,a1 增加失败。

NESTED

/**
     * Execute within a nested transaction if a current transaction exists,
     * behave like PROPAGATION_REQUIRED else. There is no analogous feature in EJB.
     * <p>Note: Actual creation of a nested transaction will only work on specific
     */

如果以后事务存在,就运行一个嵌套事务。如果不存在事务,就和 REQUIRED 一样新建一个事务。

示例 15: childTest 设置 NESTED 流传属性,伪代码如下:

  public void mainTest(String name) {A(a1);
       childTest(name);
          
   }
  @Transactional(propagation = Propagation.NESTED)
  public void childTest(String name) {B(b1);  
      throw new RuntimeException();}

在 childTest 设置 NESTED 流传属性,相当于新建一个事务,所以 b1 增加失败,mainTest 没有事务,a1 增加胜利。

示例 16:设置 mainTest 流传属性为 REQUIRED,新建一个事务,并在办法最初抛出异样。childTest 设置属性为 NESTED,伪代码如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {A(a1);
       childTest(name);
       throw new RuntimeException();}
  @Transactional(propagation = Propagation.NESTED)
  public void childTest(String name) {B(b1);         
      B2(b2);
   }

childTest 是一个嵌套的事务,当主事务的抛出异样时,嵌套事务也受影响,即 a1、b1 和 b2 都增加失败。和 示例 10不同的是,示例 10不会影响 childTest 事务。

  • NESTED 和 REQUIRED_NEW 的区别:

    • REQUIRED_NEW 是开启一个新的事务,和调用的事务无关。调用方回滚,不会影响到 REQUIRED_NEW 事务。
    • NESTED 是一个嵌套事务,是调用方的一个子事务,如果调用方事务回滚,NESTED 也会回滚。

示例 17:和 示例 16一样,在 mainTest 设置流传属性为 REQUIRED,childTest 设置流传属性为 NESTED,不同的是,在 mainTest 捕捉 childTest 抛出的异样,伪代码如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {A(a1);
      try {childTest(name);
       } catch (RuntimeException e) {e.printStackTrace();
       } 
   }
  @Transactional(propagation = Propagation.NESTED)
  public void childTest(String name) {B(b1);         
      B2(b2);
      throw new RuntimeException();}

childTest 是一个子事务,报错回滚,b1 和 b2 都增加失败。而 mainTest 捕捉了异样,不受异样影响,a1 增加胜利。

示例 18:将 示例 2 革新一下,mainTest 捕捉 childTest 抛出的异样,伪代码如下:

  @Transactional(propagation = Propagation.REQUIRED)
  public void mainTest(String name) {A(a1);
      try {childTest(name);
       } catch (RuntimeException e) {e.printStackTrace();
       } 
   }
  @Transactional(propagation = Propagation.REQUIRED)
  public void childTest(String name) {B(b1);         
      B2(b2);
      throw new RuntimeException();}

mainTest 和 childTest 两个办法都处于同一个事务,如果有一个办法报错回滚,并且被捕捉。整个事务如果还有数据增加就会抛出 Transaction rolled back because it has been marked as rollback-only 异样,同一个事务,不能呈现有的回滚了,有的不回滚,要么一起回滚,要不一起执行胜利。所以全副数据都增加失败。

  • 比照 示例 17 示例 18,NESTED 和 REQUIRED 的区别:

    • REQUIRED 流传属性表明调用方和被调用方都是应用同一个事务,被调用方出现异常,无论异样是否被捕捉,因为属于同一个事务,只有产生异样,事务都会回滚。
    • NESTED 被调用方出现异常,只有异样被捕捉,只有被调用方事务回滚,调用方事务不受影响。

总结

流传属性 总结
REQUIRED 默认属性,所有的事务都处于同一个事务下,出现异常,不论是否捕捉所有事务回滚
SUPPORTS 如果不存事务,就以非事务的形式运行,存在事务就退出该事务
MANDATORY 强制调用方增加事务,如果不存在事务就报错,存在事务就退出该事务
REQUIRES_NEW 无论调用方是否存在事务,都会创立新的事务,并且调用方异样不会影响 REQUIRES_NEW 事务
NOT_SUPPORTED 无论是否调用方是否存在事务,都是以非事务的形式执行,出现异常也会回滚
NEVER 不必事务,存在事务就报错,和 MANDATORY 相同
NESTED 嵌套事务,新建一个子事务,事务执行互相独立,如果调用方出现异常,间接回滚

测试源码

  • 流传属性源码

参考

  • 带你读懂 Spring 事务——事务的流传机制

如果感觉文章对你有帮忙的话,请点个赞吧!

正文完
 0