Spring @Transactional属性是否适用于私有方法?

Juh*_*älä 184 java spring annotations transactions

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

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

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

  }
}

...

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();
Run Code Online (Sandbox Code Playgroud)

ska*_*man 212

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

这在Spring手册第10.5.6节中有记录:

方法可见性和 @Transactional

使用代理时,应仅将@Transactional注释应用于具有公共可见性的方法.如果使用注释注释protected,private或package-visible方法,则 @Transactional不会引发错误,但带注释的方法不会显示已配置的事务设置.如果需要注释非公共方法,请考虑使用AspectJ(见下文).


Ral*_*lph 149

该问题不是私人的或公开的,问题是:如何调用它以及您使用哪种AOP实现!

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

这有两个含义:

  • 因为不能从另一个bean调用私有方法(异常是反射),@Transactional所以不考虑它们的注释.
  • 如果该方法是公共的,但是它是从同一个bean调用的,那么它也不会被考虑(如果使用(默认)Spring Proxy AOP,则此语句只是正确的).

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

恕我直言,你应该使用aspectJ模式,而不是Spring代理,将克服这个问题.AspectJ Transactional Aspects甚至编织成私有方法(检查Spring 3.0).

  • 这两点都不一定正确.第一个是不正确的 - 私有方法*可以反射调用,但代理发现逻辑选择不这样做.第二点仅适用于基于接口的JDK代理,但不适用于基于CGLIB子类的代理. (4认同)
  • 这取决于目标是否使用接口。如果不是,则使用CGLIB。 (2认同)

Juh*_*älä 30

默认情况下,该@Transactional属性仅在从applicationContext获取的引用上调用带注释的方法时才起作用.

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

  }
}
Run Code Online (Sandbox Code Playgroud)

这将打开一个交易:

Bean bean = (Bean)appContext.getBean("bean");
bean.doTransactionStuff();
Run Code Online (Sandbox Code Playgroud)

这不会:

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();
Run Code Online (Sandbox Code Playgroud)

Spring参考:使用@Transactional

注意:在代理模式(默认设置)下,只会拦截通过代理进入的"外部"方法调用.这意味着'自调用',即目标对象中调用目标对象的其他方法的方法,即使被调用的方法被标记,也不会在运行时导致实际的事务@Transactional!

如果您希望自我调用也包含在事务中,请考虑使用AspectJ模式(见下文).在这种情况下,首先不会有代理; 相反,目标类将被"编织"(即它的字节代码将被修改),以便@Transactional在任何类型的方法上变成运行时行为.

  • 谢谢!这解释了我观察到的奇怪行为.相当直观的这个内部方法调用限制... (2认同)

Jam*_*ins 12

是的,可以在私有方法上使用@Transactional,但正如其他人所提到的,这不会开箱即用.您需要使用AspectJ.我花了一些时间来弄清楚如何使它工作.我将分享我的结果.

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

首先,添加aspectjrt的依赖项.

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

然后添加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>
Run Code Online (Sandbox Code Playgroud)

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

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
Run Code Online (Sandbox Code Playgroud)

现在,您应该能够在私有方法上使用@Transactional.

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


小智 5

Spring Docs 解释说

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

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

另一种方式是用户BeanSelfAware


loo*_*nis 5

如果您需要在事务内包装私有方法,并且不想使用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(){
        //...
    }

}
Run Code Online (Sandbox Code Playgroud)