Spring:事务传播

4

I have the following code:

@Transactional
public void handle() {
    for (Item item : getItems()) {
        handle(item);
    }
}

@Transactional(propagation = Propagation.NESTED)
public void handle(Item item) {
    /* logic here */
}

假设在handle()循环处理10个项目,其中3个项目会抛出异常。

我的问题是:

[1] 在第10个项目后,外部事务是否会被提交?这意味着7个项目所做的必要更改将被提交,而另外3个项目的中间更改将回滚到创建的保存点?

[2] handle(Item)中的异常是否会被捕获并不会被转发到handle()中?这是由@Transactional实现的吗?

[3] 另外,我想了解以下流程的行为差异:

@Transactional
public void handle() {
    for (Item item : getItems()) {
        handle(item);
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handle(Item item) {
    /* logic here */
}

根据文档,我了解到使用REQUIRES_NEW会启动一个新的事务并挂起活动事务。这与NESTED不同,后者创建保存点且当前事务仍在使用。但是我认为,在使用REQUIRES_NEW时,7项更改将被提交,而其他3项的中间更改将被忽略。
那么,真正的区别是什么呢(如果有的话)?

在这种情况下,您只有一个事务。它将被提交或回滚。没有外部事务。 - xyz
@sbjavateam 能否解释一下? - Dave Ope
在同一个服务中,您可以从一个事务方法调用另一个事务方法。Spring 事务使用代理来工作,在您的情况下,Spring 不会为第二个调用启动新的事务。 - xyz
@sbjavateam 这个应该可以和AspectJ一起工作吗? - Dave Ope
使用AspectJ - 是的 - xyz
2个回答

2

NESTED(嵌套)开启一个主事务的子事务。REQUIRES_NEW(需要新的)则是开启一个单独的事务。

如果你标记了一个使用 REQUIRES_NEW 的方法,在方法退出后,该数据会保留在数据库中(由单独的事务提交),无论外部事务发生什么情况。

而对于 NESTED,如果外部事务被回滚,则更改将被回滚。

请参见此处的答案。


我明白了。那么关于异常呢?使用NESTED或者REQUIRES_NEW@Transactional会吞掉异常(并执行回滚逻辑),以防止外部或父事务也回滚吗? - Dave Ope
Transactional不会吞噬异常。它会捕获异常,在catch中进行回滚,并重新抛出以让您处理真正的异常。因此,在您的情况下,假设第三个handle(item);引发异常。在REQUIRES_NEW的情况下,前两个调用仍然保留在数据库中;在NESTED的情况下,前两个调用与主事务一起回滚。 - StanislavL
明白了!那么有没有一种优雅的方式来吞噬异常呢?使用注释可以实现吗?在我的情况下,我希望尽可能地处理多个项目。因此,如果第3个项目崩溃,我们应该继续处理第4、5、6个项目等等。主/父事务永远不应该回滚。 - Dave Ope
handle(item); 放入 try/catch 中,并在 catch 中继续执行。为 handle(item); 添加 REQUIRES_NEW。我认为这就足够了。 - StanislavL

1

据我的理解,从文档中可以得知,在这里会启动一个新的事务,并暂停活动事务。

  1. 如果在同一服务中,方法handle()和handle(Item item)只有一个会启动事务——对于第一个方法而言。这被称为自我调用。请参见:

Spring事务管理的一个硬性规定什么是事务?

要将事务应用于handle()内的handle(Item item),您应该

    @Transactional
    public void handle() {
       for (Item item : getItems()) {
         applicationContext.getBean(your service).handle(item); 
       }
    }

or use spring aspectj load time weaving for self-invocation (inside one service call one method from another)

在循环中使用Propagation.REQUIRES_NEW调用方法会影响性能,因为您会为每个调用创建一个新事务。您可以将其包装在一个方法中,但需要处理异常 - 仅限于业务逻辑相关的异常!不是所有类型的异常。
@Transactional
public void handle() {
        ..some logic
        //if you really need it
        applicationContext.getBean(your service).handleBatch(getItems());
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handleBatch(Collection items) {
    for (Item item : items) {
        applicationContext.getBean(your service).handle(item)
    }
}

public void handle(Item item) {
   try{ 
      //do operation
      }catch(.appropriate type..Exception ex){
         //log exception!!!
   }
}

关于回滚:Spring 在 RuntimeException 和 Error 上进行回滚,但不会在检查异常上进行回滚。如果你的方法抛出一些检查异常,那么更改将被提交。为了避免提交,请使用 @Transactional(rollbackFor=CheckedException.class)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接