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

时间:2010-12-09 08:38:52

标签: java spring transactions annotations

如果我对Spring bean中的私有方法有一个@Transactional注释,那么注释是否有效?

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

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

  }
}

...

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

8 个答案:

答案 0 :(得分:205)

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

Spring Manual chapter 10.5.6中记录了这一点:

  

方法可见性和@Transactional

     

使用代理时,您应该申请   仅@Transactional注释   具有公众可见度的方法。如果   你做注释保护,私人或   包装可见的方法用   @Transactional注释,没有错误   被提出,但注释方法   没有展示配置   交易设置。考虑一下   如果需要,可以使用AspectJ(见下文)   注释非公开方法。

答案 1 :(得分:132)

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

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

这有两个含义:

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

@See Spring Reference: Chapter 9.6 9.6 Proxying mechanisms

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

答案 2 :(得分:28)

默认情况下,@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();

Spring Reference: Using @Transactional

  

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

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

答案 3 :(得分:10)

是的,可以在私有方法上使用@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构建进行测试,作为完整性检查。

答案 4 :(得分:3)

答案是否定的。请参阅Spring Reference: Using @Transactional  :

  

@Transactional注释可以放在接口定义,接口上的方法,类定义或类上的 public 方法之前

答案 5 :(得分:3)

Spring Docs解释说

  

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

     

考虑使用AspectJ模式(参见下表中的mode属性)   如果你希望自我调用包含在事务中   好。在这种情况下,首先不会有代理;   相反,目标类将被编织(即,它的字节代码将   被修改)以便将@Transactional变为运行时行为   任何一种方法。

另一种方式是用户 BeanSelfAware

答案 6 :(得分:3)

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

}

答案 7 :(得分:0)

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

1

用法:

@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") } } 是否重用现有事务,但是此代码肯定可以。