@Transactional方法调用没有@Transactional注释的另一个方法?

132

我看到一个Service类中有一个被标记为@Transactional的方法,但是它也调用了同一类中未标记@Transactional的其他方法。

这是否意味着对不同方法的调用会导致应用程序打开与数据库的单独连接或挂起父事务等操作?

没有任何注释的方法被另一个带有@Transactional注释的方法调用时,默认行为是什么?

4个回答

170

当您在事务块中调用没有@Transactional的方法时,父事务将传播到新方法。它将使用来自父方法(带有@Transactional)的相同连接,并且在被调用的方法(没有@Transactional)中引起的任何异常都会导致事务根据事务定义进行回滚。

如果您从具有@Transactional的方法调用具有@Transactional注释的方法,这两个方法属于同一个Spring Bean,则被调用方法的事务行为不会对事务产生影响。但是,如果您从具有事务定义的另一个方法调用具有事务定义的方法,并且它们属于不同的Spring Bean,则被调用方法中的代码将遵循其自己的事务定义。

您可以在Spring Transaction Management documentation声明式事务管理部分中找到更多详细信息。

Spring声明式事务模型使用AOP代理,因此AOP代理负责创建事务。仅当被调用方法属于与调用方不同的Spring Bean时,AOP代理才会处于活动状态。


@Arun,还有一个维度是事务传播属性。在@Transactional中,您可以指定传播属性,这是类型为@Propagation的枚举值。 - Tomasz Błachowicz
2
@Tomasz 是的。但是还应该提到,更改从另一个@Transactional方法调用的方法的事务传播将没有任何效果。 - Fil
1
@Tomasz,这就是我所说的“将遵循调用方法中给定的事务定义”。但是,如果调用来自同一对象实例,则不会产生任何影响,因为调用不会通过负责事务维护的aop代理传播。 - Arun P Johny
8
@Filip,这并不完全正确。如果你从另一个对象/实例中调用带有@Transactional定义的方法,即使调用方法具有不同的@Transactional属性,被调用方法也将遵循自己的事务定义。 - Arun P Johny
1
@ArunPJohny,"在同一实例中"是什么意思?例如,我有一个带有Transactional注释的父方法,在此方法中,我调用另一个来自另一个服务的带有Transactional的方法...当在子方法中发生异常时,我想回滚所有内容... - Matley
显示剩余6条评论

26
  • 这是否意味着调用不同方法会导致应用程序打开单独的数据库连接或暂停父事务等问题?

这取决于传播级别。以下是所有可能的级别

例如,如果传播级别是NESTED,则当前事务将“挂起”,并将创建一个新事务(注意:实际创建嵌套事务仅适用于特定的事务管理器)。

  • 没有任何注释的方法被带有@Transactional注释的另一个方法调用时,默认行为是什么?

默认的传播级别(你称之为“行为”)是REQUIRED。如果调用了带有@Transactional注释的“内部”方法(或通过XML进行事务声明),它将在同一个事务中执行,例如,“不会创建新的事务”。


如果NOT_SUPPORTED的子调用没有任何注释,会怎样呢?它会继承NOT_SUPPORTED还是像REQUIRED一样开启一个新事务?例如:f1.call(){ f2() },其中f1有NOT_SUPPORTED注释,而f2没有。 - Dave

13

@Transactional 标记事务的边界(开始/结束),但事务本身绑定到线程。一旦事务启动,它会在方法调用中传播,直到原始方法返回并提交/回滚事务。

如果调用了另一个带有 @Transactional 注解的方法,则传播取决于该注解的传播属性。


1
这3个答案在某种程度上相互矛盾,不确定哪一个更准确。 - Eric
1
@EricWang 我只是想分享一下,我今天测试了这种情况,Arun P Johny的答案(带有评论)对于这种内部调用场景最准确。 - Vinay Vissh

8

如果内部方法没有使用@Transactional进行注释,则会影响外部方法。

如果内部方法还使用@Transactional进行注释,并且使用了Propagation.REQUIRES_NEW,则会发生以下情况。

...
@Autowired
private TestDAO testDAO;

@Autowired
private SomeBean someBean;

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void outerMethod(User user) {
  testDAO.insertUser(user);

  try {
    someBean.innerMethod();
  } catch(RuntimeException e) {
    // handle exception
  }
}


@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerMethod() {
  throw new RuntimeException("Rollback this transaction!");
}

内部方法被注释为REQUIRES_NEW并抛出了一个RuntimeException,因此它将将其事务设置为回滚但不会影响外部事务。当内部事务开始时,外部事务被暂停,然后在内部事务结束后恢复。它们彼此独立运行,因此外部事务可能会成功提交。

9
为了让初学者更清楚,我相信 innerMethod() 需要在一个不同的 bean 上(即 Spring 管理的 Java 对象)而不是 outerMethod()。如果它们都在同一个 bean 上,我认为 innerMethod 将不会实际使用其注释中声明的 Transactional 行为。相反,它将使用 outerMethod() 声明中声明的事务行为。这是因为 Spring 如何处理 AOP,AOP 用于其 @Transactional 注释(https://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/transaction.html#tx-decl-explained)。 - johnsimer

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