Spring Transaction Propagation in a Nutshell


Spring允许您通过事务传播机制来控制逻辑和物理事务的行为。您可以在Spring应用程序中通过设置七种事务传播机制org.springframework.transaction.annotation.Propagation

默认情况下,导致事务回滚的唯一异常是未经检查的异常(如RuntimeException)。不过,你可以通过控制这方面noRollbackFor,noRollbackForClassNamerollbackFor,和rollbackForClassName元素@Transactional

Propagation.REQUIRED Propagation.REQUIRED是@Transactional注释的默认设置。的REQUIRED传播可以被解释如下:

  • 如果没有现有的物理事务,那么Spring容器将创建一个。
  • 如果存在现有的物理事务,则带有注释的方法REQUIRE将参与该物理事务。
  • 每种带有注释的方法都REQUIRED划定一个逻辑事务,并且这些逻辑事务参与同一物理事务。
  • 每个逻辑事务都有其自己的作用域,但是,在采用这种传播机制的情况下,所有这些作用域都映射到同一物理事务。
  • 因为逻辑事务的所有范围都映射到同一物理事务,所以当这些逻辑事务之一回滚时,当前物理事务的所有逻辑事务都将回滚。

考虑以下两个逻辑事务(或将其视为包含内部逻辑事务的一个外部逻辑事务):

@Transactional(propagation=Propagation.REQUIRED)
public void insertFirstAuthor() {

  Author author = new Author();
  author.setName("Joana Nimar");
  authorRepository.save(author);

  insertSecondAuthorService.insertSecondAuthor();
}
@Transactional(propagation = Propagation.REQUIRED)
public void insertSecondAuthor() {

  Author author = new Author();
  author.setName("Alicia Tom");

  authorRepository.save(author);

  if(new Random().nextBoolean()) {
    throw new RuntimeException("DummyException: this should cause rollback of both inserts!");
  }
}

步骤1:insertFirstAuthor()调用该方法时,没有物理事务。Spring创建了一个用于执行外部逻辑事务的方法(该方法的代码)。

第2步:insertSecondAuthor()从中调用时insertFirstAuthor(),存在一个现有的物理事务。因此,Spring邀请该insertSecondAuthor()方法表示的内部逻辑事务参与此物理事务。

第3步:如果RuntimeException引发了insertSecondAuthor()方法末尾的随机原因,那么Spring将回滚这两个逻辑事务。因此,什么都不会插入数据库中。

下图描述了Propagation.REQUIRED流向:

  1. 这是START表示第一方法的调用,点insertFirstAuthor();
  2. 这是第二个方法调用insertSecondAuthor()

figure-140-propagationrequired.png

捕获和处理RuntimeExceptioninsertFirstAuthor()仍然会回滚外逻辑事务。发生这种情况是因为内部逻辑事务设置了仅回滚标记,并且由于两个逻辑事务的作用域都映射到同一物理事务,所以外部逻辑事务也被回滚。Spring将静默回滚两个逻辑事务,然后引发以下异常:

org.springframework.transaction.UnexpectedRollbackException: 
Transaction silently rolled back because it has been marked as rollback-only.

外部逻辑事务需要接收,UnexpectedRollbackException以清楚地指示已执行内部逻辑事务的回滚,因此也应回滚。

Propagation.REQUIRES_NEW Propagation.REQUIRES_NEW指示Spring容器始终创建一个新的物理事务。这样的事务也可以声明自己的超时,只读和隔离级别设置,而不继承外部物理事务的特征。

下图描述了Propagation.REQUIRES_NEW流程:

figure-141-propagationrequires-new.png

请注意如何处理此方面,因为每个物理事务都需要其自己的数据库连接。因此,外部物理事务将具有其自己的数据库连接,而REQUIRES_NEW将创建内部物理事务并将绑定新的数据库连接。在同步执行中,内部物理事务在运行时,外部物理事务被挂起,并且其数据库连接保持打开状态。内部物理事务提交后,将恢复外部物理事务,继续运行并提交/回滚。

如果内部实物交易回滚,则可能会或可能不会影响外部实物交易。

@Transactional(propagation=Propagation.REQUIRED)
public void insertFirstAuthor() {

  Author author = new Author();
  author.setName("Joana Nimar");

  authorRepository.save(author);

  insertSecondAuthorService.insertSecondAuthor();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertSecondAuthor() {

  Author author = new Author();
  author.setName("Alicia Tom");

  authorRepository.save(author);

  if(new Random().nextBoolean()) {    
    throw new RuntimeException ("DummyException: this should cause rollback of second insert only!");
  }
}

步骤1:当您调用时insertFirstAuthor(),将创建第一个物理交易(外部),因为没有现有的物理交易。

步骤2:当insertSecondAuthor() 从中调用时insertFirstAuthor(),Spring将创建另一个物理事务(内部)。

步骤3:如果RuntimeException抛出,则两个物理事务(内部第一个和外部之后)都将回滚。发生这种情况是因为抛出的异常 insertSecondAuthor() 会传播到调用方,insertFirstAuthor()从而也会导致外部物理事务回滚。如果这不是理想的行为,并且您只想回退内部物理事务而不影响外部物理事务,则需要捕获并处理RuntimeExceptionin insertFirstAuthor(),如下所示:

@Transactional(propagation = Propagation.REQUIRED)
public void insertFirstAuthor() {

  Author author = new Author();
  author.setName("Joana Nimar");

  authorRepository.save(author);

  try {
    insertSecondAuthorService.insertSecondAuthor();
  } catch (RuntimeException e) {
    System.err.println("Exception: " + e);
  }
}

即使内部物理事务回滚,外部物理事务也会提交。

如果在提交内部物理事务之后回滚外部物理事务,则内部物理事务不会受到影响。

Propagation.NESTED NESTED就像一样REQUIRED,只是它在嵌套调用之间使用保存点。换句话说,内部逻辑事务可以独立于外部逻辑事务回滚。

下图描述了Propagation.NESTED流程:

figure-142-propagationnested.png

尝试NESTED与Hibernate JPA一起使用将导致Spring异常,如下所示:

NestedTransactionNotSupportedException: JpaDialect does not support savepoints 
- check your JPA provider capabilities

发生这种情况是因为Hibernate JPA不支持嵌套事务。

导致异常的Spring代码是:

private SavepointManager getSavepointManager() {
  ...
  SavepointManager savepointManager
    = getEntityManagerHolder().getSavepointManager();

  if (savepointManager == null) {
    throw new NestedTransactionNotSupportedException("JpaDialect does not support ...");
  }

  return savepointManager;
}

一种解决方案是使用JdbcTemplate或支持嵌套事务的JPA提供程序。您也可以使用jOOQ。

Propagation.MANDATORY Propagation.MANDATORY需要现有的物理事务或将导致异常,如下所示:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'.

下图描述了Propagation.MANDATORY流程:

figure-143-propagationmandatory.png

考虑以下代码:

@Transactional(propagation=Propagation.REQUIRED)
public void insertFirstAuthor() {

  Author author = new Author();
  author.setName("Joana Nimar");

  authorRepository.save(author);

  insertSecondAuthorService.insertSecondAuthor();
}
@Transactional(propagation = Propagation.MANDATORY)
public void insertSecondAuthor() {

  Author author = new Author();
  author.setName("Alicia Tom");

  authorRepository.save(author);


  if (new Random().nextBoolean()) {
    throw new RuntimeException("DummyException: this should cause rollback of both inserts!");
  }
}

insertSecondAuthor()从调用insertFirstAuthor(),有一种现有的物理交易(通过创建Propagation.REQUIRED)。此外,由insertSecondAuthor()代码表示的内部逻辑事务将参与此物理事务。如果此内部逻辑事务回滚,则外部逻辑事务也将回滚,与的情况完全相同Propagation.REQUIRED

Propagation.NEVER Propagation.NEVER指出不应该存在任何实物交易的状态。如果找到实物交易,NEVER则将导致如下异常:

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

下图描述了Propagation.NEVER流程: figure-144-propagationnever.png

查看以下代码:

@Transactional(propagation = Propagation.NEVER)
public void insertFirstAuthor() {

  Author author = new Author();
  author.setName("Joana Nimar");

  authorRepository.save(author);
}

第1步: 当insertFirstAuthor()被调用时,弹簧搜索现有的实物交易。

第2步:由于没有可用的Spring,因此Spring将不会导致异常,并且将在物理事务之外运行此方法的代码。

步骤3:当代码到达save()方法时,Spring将打开一个物理事务,尤其是用于运行此调用的事务。发生这种情况是因为save()利用了default Propagation.REQUIRED

当调用带有注释的方法时NEVER,必须确保没有打开任何物理事务。此方法中的代码可以毫无问题地打开物理事务。

Propagation.NOT_SUPPORTED Propagation.NOT_SUPPORTED声明如果存在实物交易,则将其暂停,然后再继续。此实际交易将在最后自动恢复。恢复该事务后,可以回滚(如果发生故障)或提交该事务。

下图描述了Propagation.NOT_SUPPORTED流程: figure-145-propagationnot-supported.png

让我们看一些代码:

@Transactional(propagation = Propagation.REQUIRED)
public void insertFirstAuthor() {

  Author author = new Author();
  author.setName("Joana Nimar");

  authorRepository.save(author);

  insertSecondAuthorService.insertSecondAuthor();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void insertSecondAuthor() {

  Author author = new Author();

  author.setName("Alicia Tom");  

  authorRepository.save(author);

  if (new Random().nextBoolean()) {
    throw new RuntimeException("DummyException: this should cause "
                               + "rollback of the insert triggered in insertFirstAuthor() !");
  }
}

步骤1:当insertFirstAuthor()被调用时,没有可用的物理交易。因此,Spring将创建一个符合以下条件的事务:Propagation.REQUIRED.

步骤2:此外,代码触发插入(作者Joana Nimar保留在数据库中)。

步骤3:insertSecondAuthor()从调用该语句insertFirstAuthor(),Spring必须评估的存在Propagation.NOT_SUPPORTED。现有的实物交易;因此,在继续之前,Spring将暂停它。

步骤4:在insertSecondAuthor()任何物理事务之外执行来自的代码,直到流程触及save()调用为止。默认情况下,此方法处于Propagation.REQUIRED保护范围之内;因此,Spring创建一个物理事务,执行INSERT(对于Alicia Tom),并提交此事务。

步骤5:将insertSecondAuthor()剩余的代码被执行的物理事务之外。

步骤6: 在后 insertSecondAuthor() 代码完成,弹簧恢复暂停的物理交易和恢复的执行insertFirstAuthor()它离开的地方逻辑事务。如果将RuntimeException抛出insertSecondAuthor(),则此异常将在中传播insertFirstAuthor(),并且此逻辑事务将回滚。

即使由于暂停了事务Propagation.NOT_SUPPORTED,您仍应努力避免长时间运行的任务。请注意,在事务挂起时,附加的数据库连接仍处于活动状态,因此池连接无法重用它。换句话说,即使数据库连接的绑定事务被挂起,它也处于活动状态:

...
Suspending current transaction
HikariPool-1 - Pool stats (total=10, active=1, idle=9, waiting=0)
Resuming suspended transaction after completion of inner transaction

Propagation.SUPPORTS

Propagation.SUPPORTS声明如果存在物理事务,则它将在此物理事务的上下文中将划定的方法作为逻辑事务执行。否则,它将在物理事务之外执行此方法。让我们看一些代码:

@Transactional(propagation = Propagation.REQUIRED)
public void insertFirstAuthor() {

  Author author = new Author();
  author.setName("Joana Nimar");

  authorRepository.save(author);

  insertSecondAuthorService.insertSecondAuthor();
}
@Transactional(propagation = Propagation.SUPPORTS)
public void insertSecondAuthor() {

  Author author = new Author();
  author.setName("Alicia Tom");


  authorRepository.save(author);

  if (new Random().nextBoolean()) {
    throw new RuntimeException("DummyException: this should cause rollback of both inserts!");
  }
}

步骤1:当insertFirstAuthor()被调用时,没有可用的物理交易。因此,Spring将创建一个符合的事务Propagation.REQUIRED

步骤2:此外,Spring开始执行该insertFirstAuthor()方法表示的外部逻辑事务,并通过该方法触发插入 save() (作者Joana Nimar保留在数据库中)。

步骤3:insertSecondAuthor()从中调用insertFirstAuthor(),Spring必须评估的存在Propagation.SUPPORTS。存在现有的实物交易。因此,from的代码insertSecondAuthor()在此物理事务的上下文中作为内部逻辑事务执行。如果RuntimeException抛出a,则内部和外部逻辑事务都会回滚。

捕获和处理RuntimeExceptionininsertFirstAuthor()仍会回滚外部逻辑事务。发生这种情况是因为内部逻辑事务设置了仅回滚 标记,并且两个逻辑事务的范围都映射到同一物理事务。

下图描述了Propagation.SUPPORTS流程:

figure-146-propagationsupports.png

让我们@Transactional(propagation = Propagation.REQUIRED)从中删除insertFirstAuthor()并再次评估流程:

第1步:当 insertFirstAuthor()被调用时,没有实物交易可用,因为春天不会创建一个@Transactional缺失。

步骤2:从此代码insertFirstAuthor()开始在物理事务之外执行,直到流程触及save()调用为止。默认情况下,此方法处于保护Propagation.REQUIRED之下,因此Spring将创建一个物理事务,执行插入操作(对于Joana Nimar),然后提交此事务。

第3步:当insertSecondAuthor()从叫insertFirstAuthor(),春天将需要评估的存在Propagation.SUPPORTS。目前没有实际交易,Spring不会创建符合该Propagation.SUPPORTS定义的交易。

步骤4:直到流程中的save()方法生效为止insertSecondAuthor(),代码将在物理事务之外执行。默认情况下,save()它处于保护Propagation.REQUIRED 之下,因此Spring创建一个物理事务,执行插入操作(对于Alicia Tom),并提交此事务。

第5步:当RuntimeException被抛出,没有实物交易,所以没有被回滚。


原文链接:http://codingdict.com