【问题标题】: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();

【问题讨论】:

    标签: java spring transactions annotations


    【解决方案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,其自身的实例包装到代理中)。您可以在stackoverflow.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是否重用现有事务,但这段代码肯定可以。

      【讨论】:

        猜你喜欢
        • 2015-05-24
        • 1970-01-01
        • 2020-08-15
        • 2017-09-01
        • 1970-01-01
        • 2023-03-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多