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

Posted

技术标签:

【中文标题】Spring @Transactional 属性是不是适用于私有方法?【英文标题】:Does Spring @Transactional attribute work on a private method?Spring @Transactional 属性是否适用于私有方法? 【发布时间】:2011-05-22 17:30:56 【问题描述】:

如果我在 Spring bean 中的私有方法上有一个 @Transactional -annotation,这个注解有什么作用吗?

如果@Transactional 注释在公共方法上,它会起作用并打开一个事务。

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

  


...

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

【问题讨论】:

【参考方案1】:

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

这记录在Spring Manual chapter 10.5.6:

方法可见性和@Transactional

使用代理时,您应该申请 仅限 @Transactional 注释 到具有公共可见性的方法。如果 您确实注释了受保护的、私有的或 包可见的方法 @Transactional注解,没有错误 被提出,但带注释的方法 不显示配置的 交易设置。考虑 如果需要,使用 AspectJ(见下文) 注释非公共方法。

【讨论】:

你确定吗?我不指望它会有所作为。 代理风格如果是Cglib呢? 我使用下面的正则表达式@Transactional([^](?!public))+ \ 来查找我们代码库中可能没有任何影响的注释(因为它们在私有、受保护、包私有方法上)。没有找到“没有代理的自我引用”——当然是对公共方法的调用——是否有插件或其他东西可以发现这些?【参考方案2】:

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

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

【讨论】:

【参考方案3】:

默认情况下,@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!

,也不会在运行时导致实际事务>

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

【讨论】:

你的意思是 bean = new Bean();? 不。如果我使用 new Bean() 创建 bean,则至少在不使用 Aspect-J 的情况下,注释将永远无法工作。 谢谢!这解释了我观察到的奇怪行为。这个内部方法调用限制非常反直觉......【参考方案4】:

Spring Docs 解释说

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

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

另一种方式是用户BeanSelfAware

【讨论】:

您可以添加对BeanSelfAware 的引用吗?它看起来不像 Spring 的类 @asgs 假设,它是关于自我注入(提供一个 bean,其自身的实例包装到代理中)。您可以在***.com/q/3423972/355438 中查看示例。【参考方案5】:

问题不是私有的或公开的,问题是:它是如何被调用的,你使用的是哪个 AOP 实现!

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

这有两个含义:

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

@见Spring Reference: Chapter 9.6 9.6 Proxying mechanisms

恕我直言,您应该使用 aspectJ 模式,而不是 Spring 代理,这样可以解决问题。并且 AspectJ 事务方面甚至被编织到私有方法中(检查了 Spring 3.0)。

【讨论】:

这两点不一定都是真的。第一个是不正确的——私有方法可以被反射调用,但是代理发现逻辑选择不这样做。第二点仅适用于基于接口的 JDK 代理,而不适用于基于 CGLIB 子类的代理。 @skaffman: 1 - 我让我的陈述更准确,2. 但是默认代理是基于接口的 - 不是吗? 这取决于目标是否使用接口。如果没有,则使用 CGLIB。 可以告诉我为什么 cglib 不能但 aspectj 可以的原因或一些参考? 来自答案块中链接的引用,如果您想使用 Spring Proxies [默认环境],请在 doStuff() 上添加注释并使用 ((Bean) AopContext.currentProxy()) 调用 doPrivateStuff() .doPrivateStuff();如果传播被重复[默认环境],它将在同一个事务中执行这两种方法。【参考方案6】:

是的,可以在私有方法上使用@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,不需要实现方法应该是public的接口,那么可以在private方法上使用@Transactional吗? 是的,它适用于私有方法,并且没有接口!只要AspectJ配置得当,基本保证了工作方法装饰器。 user536161 在他的回答中指出,它甚至可以用于自调用。真的很酷,只是有点吓人。【参考方案7】:

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

@Service
public class MyService 
    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process() 
        transactionTemplate.executeWithoutResult(status -> processInTransaction());
    

    private void processInTransaction()
        //...
    

【讨论】:

很好地展示TransactionTemplate的用法,但请调用第二种方法..RequiresTransaction而不是..InTransaction。总是说出你想在一年后阅读它的方式。此外,我认为它是否真的需要第二个私有方法:要么将其内容直接放在匿名 execute 实现中,要么如果它变得混乱,则可能表明将实现拆分为另一个服务,然后您可以注释@Transactional. @Stuck,第二种方法确实没有必要,但它回答了原始问题,即如何在私有方法上应用弹簧事务 是的,我已经对答案投了赞成票,但想分享一些关于如何应用它的背景和想法,因为我认为从架构的角度来看,这种情况可能表明存在设计缺陷。【参考方案8】:

与@loonis suggested 使用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是否重用现有事务,但这段代码肯定可以。

【讨论】:

以上是关于Spring @Transactional 属性是不是适用于私有方法?的主要内容,如果未能解决你的问题,请参考以下文章

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

事务隔离属性spring传播属性 @Transactional注解

coding++:Spring中的@Transactional(rollbackFor = Exception.class)属性详解

@Transactional的参数意义及使用。spring中事务注解的配置情况

Spring声明式事务@Transactional 详解,事务隔离级别和传播行为

事务@Transactional@Transactional注解的rollbackFor属性