在Spring Bean中启动新事务

54

我们有:

@Transactional(propagation = Propagation.REQUIRED)
public class MyClass implementes MyInterface { ...

MyInterface有一个方法:go()

当执行go()时,我们会启动一个新的事务,在方法完成时进行提交/回滚 - 这很好。

现在假设在go()中,我们调用了MyClass中具有@Transactional(propagation = Propagation.REQUIRES_NEW的私有方法。似乎Spring“忽略”REQUIRES_NEW注释,并且不会启动新事务。我相信这是因为Spring AOP在接口级别(MyInterface)上运行,并且不拦截对MyClass方法的任何调用。这样说是否正确?

有没有办法在go()事务内启动新事务?唯一的方法是调用另一个已配置为REQUIRES_NEW的Spring管理bean吗?


更新:添加说明,当客户端执行go()时,他们是通过接口引用而不是类来执行的:

@Autowired
MyInterface impl;

impl.go();
3个回答

93

根据Spring 2.5参考文档:

使用代理时,@Transactional注解只应用于公共可见性的方法。如果您使用@Transactional注解保护、私有或软件包可见性方法,则不会引发错误,但是注解方法将不会表现出已配置的事务设置。

因此,Spring忽略非公开方法上的@Transactional注解。

此外,

在代理模式下(默认情况下),仅拦截通过代理传入的“外部”方法调用。这意味着“自我调用”,即目标对象中的一个方法调用目标对象的其他方法,在运行时不会导致实际事务,即使被调用的方法标记为@Transactional

因此,即使您将方法设置为public,从同一类的方法中调用它也不会启动新的事务。

您可以在事务设置中使用aspectj模式,以便将与事务相关的代码编织到类中,并且在运行时不创建代理。

有关更多详细信息,请参见参考文档

另一种可能的做法是在类本身中获取该类的Spring代理并调用其方法,而不是使用this

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class SomeService {

    @Autowired
    private ApplicationContext applicationContext;

    private SomeService  getSpringProxy() {
        return applicationContext.getBean(this.getClass());
    }

    private void doSomeAndThenMore() {
        // instead of
        // this.doSometingPublicly();
        // do the following to run in transaction
        getSpringProxy().doSometingPublicly();
    }

    public void doSometingPublicly() {
        //do some transactional stuff here
    }

}

57
@Transactional 只有在一个 public 的方法上才会被 Spring AOP 发现。

不过如果你想的话,可以使用 TransactionTemplate 来编程式地开启一个新事务。例如:这里

TransactionTemplate txTemplate = new TransactionTemplate(txManager);                
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(status -> {
        // do stuff
});

但是,即使@Transactional在MyClass的公共方法上,似乎Spring也不会拾取它,除非该方法在接口中定义 - 正确吗? - Marcus Leon
添加一点,这是因为调用所提到的go()方法的客户端具有对接口而不是类的引用。 - Marcus Leon
4
@Marcus: 有点像。如果MyClass实现了一个接口,那么Spring将仅使用该接口来生成事务代理,甚至会忽略公共的非接口方法。但是,如果MyClass没有实现任何接口,则所有公共方法都将被使用。 - skaffman
重要的不是它是否是一个接口,而是它需要在代理后面,作为一个被注入的@Autowired依赖项。 - flob

10
简而言之,你需要通过代理调用方法来实现事务行为。在同一个bean中调用“REQUIRES_NEW”是可能的,正如问题所问。要做到这一点,您必须创建一个“self”引用。在Spring中,这并不直观。您需要使用@Resource注释来注入它。
@Service("someService")
public class ServieImpl implements Service {

   @Resource(name = "someService")
   Service selfReference;

   @Transactional
   public void firstMethod() {
       selfReference.secondMethod();
   }

   @Transactional(propagation = Propagation.REQUIRES_NEW) 
   public void secondMethod() {    
         //do in new transaction
   }

} 

在firstMethod中的调用调用了代理而不是"this",这应该使"REQUIRES_NEW"事务起作用。


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