Spring @Transactional属性可以在私有方法上工作吗?


196

如果我在Spring bean的私有方法上有一个@Transactional -annotation,那么注释是否有效果?

如果@Transactional注释在公共方法上,则它可以工作并打开一个事务。

public class Bean {
  public void doStuff() {
     doPrivateStuff();
  }
  @Transactional
  private void doPrivateStuff() {

  }
}

...

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

Answers:


163

问题不是私有的还是公共的,问题是:如何调用它以及您使用哪种AOP实现!

如果您使用(默认)Spring Proxy AOP,则@Transational仅当调用通过代理时,才会考虑Spring提供的所有AOP功能(如)。-如果从另一个 bean 调用带注释的方法,通常就是这种情况。

这有两个含义:

  • 因为不能从另一个bean调用私有方法(例外是反射),@Transactional所以不考虑它们的注释。
  • 如果该方法是公共方法,但是从同一个bean调用,则也不会考虑该方法(仅当使用(默认)Spring Proxy AOP时,此语句才是正确的)。

@See Spring参考:第9.6章9.6代理机制

恕我直言,您应该使用AspectJ模式而不是Spring Proxies,它将克服此问题。而且AspectJ事务性方面甚至被编织成私有方法(已检查Spring 3.0)。


4
这两点不一定都是正确的。第一个是不正确的-私有方法可以反射地调用,但是代理发现逻辑选择不这样做。第二点仅适用于基于接口的JDK代理,不适用于基于CGLIB子类的代理。
skaffman 2010年

@skaffman:1-我的陈述更加准确,2。但是默认代理是基于接口的-是吗?
拉尔夫(Ralph)2010年

2
这取决于目标是否使用接口。如果不是,则使用CGLIB。
skaffman 2010年

canu告诉我Reson或一些参考资料,为什么cglib不能但AspectJ可以?
菲尔2014年

1
如果您想使用Spring Proxies [默认环境],请从答案块中的链接引用,将注释放在doStuff()上,并使用((Bean)AopContext.currentProxy())调用doPrivateStuff()。doPrivateStuff(); 如果重新传播[默认环境],它将在同一事务中执行这两种方法。
Michael Ouyang

219

您的问题的答案是否定的- @Transactional如果用于注释私有方法,则将无效。代理生成器将忽略它们。

Spring手册第10.5.6章中有记录:

方法可见性和 @Transactional

使用代理时,应仅将@Transactional注释应用于具有公共可见性的方法。如果使用注释对受保护的,私有的或程序包可见的方法进行 @Transactional注释,则不会引发任何错误,但是带注释的方法不会显示配置的事务设置。如果需要注释非公共方法,请考虑使用AspectJ(请参见下文)。


你确定吗?我不希望它会有所作为。
willcodejavaforfood 2010年

如果代理样式是Cglib呢?
百合

32

默认情况下,该@Transactional属性仅在对从applicationContext获得的引用上调用带注释的方法时有效。

public class Bean {
  public void doStuff() {
    doTransactionStuff();
  }
  @Transactional
  public void doTransactionStuff() {

  }
}

这将打开一个事务:

Bean bean = (Bean)appContext.getBean("bean");
bean.doTransactionStuff();

这不会:

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

春季参考:使用@Transactional

注意:在代理模式下(默认),将仅拦截通过代理进入的“外部”方法调用。这意味着“自调用”,即目标对象内的一种调用目标对象其他方法的方法,即使被调用的方法标有@Transactional!,也不会在运行时导致实际事务。

如果您希望自调用也与事务包装在一起,请考虑使用AspectJ模式(请参见下文)。在这种情况下,首先不会有代理。相反,将对目标类进行“编织”(即,将修改其字节码),以将其@Transactional转换为任何方法的运行时行为。


您是说bean = new Bean();吗?
willcodejavaforfood 2010年

不。如果我使用new Bean()创建bean,则至少在不使用Aspect-J的情况下,注释将永远无法工作。
JuhaSyrjälä2010年

2
谢谢!这解释了我观察到的奇怪行为。非常直观的内部方法调用限制...
manuel aldana

我了解到“只有通过代理
传入的

13

是的,可以在私有方法上使用@Transactional,但是正如其他人提到的那样,这将无法立即使用。您需要使用AspectJ。我花了一些时间弄清楚如何使其工作。我将分享我的结果。

我选择使用编译时编织而不是加载时编织,因为我认为这是总体上更好的选择。另外,我使用的是Java 8,因此您可能需要调整一些参数。

首先,添加aspectjrt的依赖项。

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

然后添加AspectJ插件在Maven中进行实际的字节码编织(这可能不是一个最小的示例)。

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.8</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

最后将此添加到您的配置类

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

现在您应该可以在私有方法上使用@Transactional了。

这种方法的一个警告:您将需要配置IDE以了解AspectJ,否则,例如,如果您通过Eclipse运行该应用程序,则可能无法正常工作。确保针对直接的Maven构建进行测试以进行完整性检查。


如果代理方法是cglib,则无需实现该方法应为公共方法的接口,那么它可以在私有方法上使用@Transactional吗?
莉莉

是的,它适用于私有方法,并且没有接口!只要AspectJ配置正确,它就基本上可以保证工作方法装饰器。用户536161在他的回答中指出,它甚至可以在自调用上运行。这真的很酷,而且有点吓人。
詹姆士·沃特金斯

12

如果您需要在事务内包装私有方法并且不想使用Aspectj,则可以使用TransactionTemplate

@Service
public class MyService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process(){
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                processInTransaction();
            }
        });

    }

    private void processInTransaction(){
        //...
    }

}

很好地显示TransactionTemplate用法,但请调用第二个方法..RequiresTransaction而不是..InTransaction。总是在一年后以自己喜欢的方式命名。我还要争论一下,如果它确实需要第二种私有方法:要么将其内容直接放在匿名execute实现中,要么变得混乱,这可能是将实现拆分为另一个可以注释的服务的指示@Transactional
卡在

@Stuck,确实没有必要使用第二种方法,但是它回答了最初的问题,即如何在私有方法上应用春季交易
loonis

是的,我已经同意了这个答案,但是想分享一些背景知识和关于如何应用的想法,因为从架构的角度来看,这种情况可能表明存在设计缺陷。
卡在

5

Spring Docs解释说

在代理模式(默认)下,仅拦截通过代理传入的外部方法调用。这意味着自调用实际上是目标对象中调用目标对象另一种方法的方法,即使调用的方法标记有@Transactional,也不会在运行时导致实际事务。

如果希望自调用也与事务包装在一起,请考虑使用AspectJ模式(请参见下表中的mode属性)。在这种情况下,首先将没有代理。相反,将对目标类进行编织(即,将修改其字节码),以便将@Transactional转换为任何方法上的运行时行为。

另一种方法是用户BeanSelfAware


您可以添加参考BeanSelfAware吗?它看起来不像是Spring的课程
-asgs

@asgs假设它是关于自我注入的(提供一个将其实例包装到代理中的bean)。您可以在stackoverflow.com/q/3423972/355438中查看示例。
Lu55


1

@loonis建议使用TransactionTemplate的方法相同,可以使用此帮助程序组件(Kotlin):

@Component
class TransactionalUtils {
    /**
     * Execute any [block] of code (even private methods)
     * as if it was effectively [Transactional]
     */
    @Transactional
    fun <R> executeAsTransactional(block: () -> R): R {
        return block()
    }
}

用法:

@Service
class SomeService(private val transactionalUtils: TransactionalUtils) {

    fun foo() {
        transactionalUtils.executeAsTransactional { transactionalFoo() }
    }

    private fun transactionalFoo() {
        println("This method is executed within transaction")
    }
}

不知道是否TransactionTemplate重用现有事务,但是此代码确实可以。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.