簡體   English   中英

Spring @Transactional 屬性是否適用於私有方法?

[英]Does Spring @Transactional attribute work on a private method?

如果我在 Spring bean 中的私有方法上有@Transactional注釋,該注釋有什么作用嗎?

如果@Transactional注釋在公共方法上,它會起作用並打開一個事務。

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

  }
}

...

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

您的問題的答案是否定的 - 如果用於注釋私有方法,@ @Transactional將無效。 代理生成器將忽略它們。

這在Spring 手冊第 10.5.6 章中有記錄:

方法可見性和@Transactional

使用代理時,您應該僅將@Transactional注釋應用於具有公共可見性的方法。 如果您使用@Transactional注釋對受保護的、私有的或包可見的方法進行注釋,則不會引發錯誤,但帶注釋的方法不會顯示配置的事務設置。 如果您需要注釋非公共方法,請考慮使用 AspectJ(見下文)。

問題不是私有的或公開的,問題是:它是如何調用的以及您使用的是哪種 AOP 實現!

如果您使用(默認)Spring Proxy AOP,那么只有在調用通過代理時才會考慮 Spring 提供的所有 AOP 功能(如@Transactional )。 -- 如果注釋的方法是從另一個bean 調用的,則通常是這種情況。

這有兩個含義:

  • 因為不能從另一個 bean 調用私有方法(反射是例外),所以不考慮它們的@Transactional Annotation。
  • 如果該方法是公共的,但它是從同一個 bean 調用的,則也不會考慮它(此語句僅在使用(默認)Spring Proxy AOP 時才正確)。

@See Spring 參考:第 9.6 章 9.6 代理機制

恕我直言,您應該使用 aspectJ 模式,而不是 Spring Proxies,這將克服這個問題。 並且 AspectJ 事務方面甚至被編織到私有方法中(檢查 Spring 3.0)。

默認情況下,@ @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 參考:使用 @Transactional

注意:在代理模式下(默認),只有通過代理進入的“外部”方法調用才會被攔截。 這意味着“自調用”,即目標對象中調用目標對象的其他方法的方法,即使被調用的方法被標記為@Transactional ,也不會在運行時導致實際事務!

如果您希望自調用也與事務一起包裝,請考慮使用 AspectJ 模式(見下文)。 在這種情況下,首先不會有代理; 相反,目標類將被“編織”(即其字節碼將被修改),以便將@Transactional轉換為任何類型方法的運行時行為。

如果您需要在事務中包裝私有方法並且不想使用 AspectJ,則可以使用TransactionTemplate

@Service
public class MyService {
    @Autowired
    private TransactionTemplate transactionTemplate;

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

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

是的,可以在私有方法上使用 @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 構建進行測試作為健全性檢查。

Spring Docs 解釋說

在代理模式下(默認),只有通過代理進入的外部方法調用才會被攔截。 這意味着自調用實際上是目標對象中的一個方法調用目標對象的另一個方法,即使被調用的方法用@Transactional 標記,也不會在運行時導致實際事務。

如果您希望自調用也與事務一起包裝,請考慮使用 AspectJ 模式(請參閱下表中的模式屬性)。 在這種情況下,首先不會有代理; 相反,目標類將被編織(即,其字節碼將被修改),以便將 @Transactional 轉換為任何類型方法的運行時行為。

另一種方式是用戶BeanSelfAware

答案是不。 請參閱Spring 參考:使用 @Transactional

@Transactional注解可以放在接口定義、接口上的方法、類定義或類上的公共方法之前

@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是否重用現有的事務,但這段代碼肯定可以。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM