自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

我們一起聊聊 Spring 是如何管理事務(wù)的,你學(xué)會(huì)了嗎?

開發(fā) 前端
本文主要討論了 Spring 編程式和聲明式兩種管理事務(wù)的方式以及 @Transactional? 注解的使用和常見問題分析,下篇我將會(huì)從源碼的角度分析 @Transactional 注解的解析(代理生成)以及事務(wù)的執(zhí)行過程。?

本篇我們事務(wù)中的基本概念開始,討論下在 Spring 框架中是如何實(shí)現(xiàn)事務(wù)管理的,并重點(diǎn)講解基于 @Transactional 注解的聲明式事務(wù)的實(shí)際應(yīng)用。本文僅涉及概念性的知識(shí),原理解析將會(huì)另起一篇單獨(dú)進(jìn)行介紹。

事務(wù)的基礎(chǔ)知識(shí)

事務(wù)(Transaction)是數(shù)據(jù)庫管理系統(tǒng)中一組邏輯操作的集合,這些操作作為一個(gè)整體被對待,不可拆分,以保證數(shù)據(jù)的一致性和完整性。事務(wù)具有四個(gè)關(guān)鍵屬性,通常被稱為 ACID 特性。

ACID 特性

ACID 指的是事務(wù)的四個(gè)基本特性:

  • 原子性(Atomicity):一個(gè)事務(wù)中的所有操作要么全部完成,要么完全不執(zhí)行。如果事務(wù)的一部分失敗,則整個(gè)事務(wù)都會(huì)被回滾。例如,在銀行轉(zhuǎn)賬的場景中,從賬戶 A 轉(zhuǎn)賬到賬戶 B 需要兩個(gè)步驟:減少 A 的余額和增加 B 的余額。這兩個(gè)步驟必須作為一個(gè)整體成功或失敗,否則可能會(huì)導(dǎo)致資金丟失或重復(fù)。
  • 一致性(Consistency):事務(wù)必須保證數(shù)據(jù)庫從一個(gè)一致狀態(tài)轉(zhuǎn)換到另一個(gè)一致狀態(tài),即事務(wù)執(zhí)行前后數(shù)據(jù)完整性約束沒有被破壞。還是以銀行轉(zhuǎn)賬為例,一致性確保在轉(zhuǎn)賬前后,兩個(gè)賬戶的總金額保持不變,即使發(fā)生故障也不會(huì)影響這一原則。
  • 隔離性(Isolation):并發(fā)執(zhí)行的多個(gè)事務(wù)之間不會(huì)互相干擾。每個(gè)事務(wù)都應(yīng)該獨(dú)立地運(yùn)行,就好像它是系統(tǒng)中唯一存在的事務(wù)一樣。假設(shè)同時(shí)有兩個(gè)轉(zhuǎn)賬請求,一個(gè)是 A 向 B 轉(zhuǎn)賬,另一個(gè)是 C 向 D 轉(zhuǎn)賬。這兩個(gè)事務(wù)應(yīng)該互不影響,它們的結(jié)果應(yīng)該是各自獨(dú)立且正確的。
  • 持久性(Durability):一旦事務(wù)提交,它對數(shù)據(jù)庫所做的更改就是永久性的,即使系統(tǒng)發(fā)生故障也不會(huì)丟失這些更改。比如,當(dāng)銀行轉(zhuǎn)賬完成后,即使服務(wù)器突然斷電,轉(zhuǎn)賬記錄也應(yīng)當(dāng)保存下來,確保用戶的資金變動(dòng)信息不會(huì)丟失。

其他特性

除了 ACID 特性之外,還有以下幾個(gè)重要的事務(wù)屬性:

  • 事務(wù)回滾(Rollback):當(dāng)事務(wù)遇到錯(cuò)誤或異常時(shí),可以撤銷所有已經(jīng)執(zhí)行的操作,使數(shù)據(jù)庫恢復(fù)到事務(wù)開始前的狀態(tài)。這是保證事務(wù)原子性和一致性的關(guān)鍵手段。例如,在一個(gè)復(fù)雜的業(yè)務(wù)流程中,如果其中一步驟失敗,整個(gè)事務(wù)將被回滾,確保之前的所有變更都取消,從而維持系統(tǒng)的穩(wěn)定狀態(tài)。
  • 事務(wù)超時(shí)(Timeout):為事務(wù)設(shè)定一個(gè)最大允許執(zhí)行時(shí)間,超過這個(gè)時(shí)間則自動(dòng)終止事務(wù),以防止長時(shí)間占用資源。這對于避免死鎖和提高系統(tǒng)響應(yīng)速度非常重要。例如,在高并發(fā)環(huán)境中,某些長時(shí)間運(yùn)行的事務(wù)可能導(dǎo)致資源鎖定,影響其他事務(wù)的正常進(jìn)行。通過設(shè)置合理的超時(shí)值,可以及時(shí)釋放資源,保證系統(tǒng)的流暢運(yùn)行。
  • 只讀事務(wù)(Read-only Transactions):某些場景下,事務(wù)只需要讀取數(shù)據(jù)而不需要修改,這時(shí)可以聲明事務(wù)為只讀模式以優(yōu)化性能。只讀事務(wù)告訴數(shù)據(jù)庫引擎當(dāng)前事務(wù)不會(huì)修改任何數(shù)據(jù),因此它可以采用更高效的查詢策略,如跳過某些類型的鎖檢查。這不僅提高了查詢的速度,還減少了對共享資源的競爭壓力。

并發(fā)事務(wù)中存在的問題

當(dāng)多個(gè)事務(wù)同時(shí)訪問同一份數(shù)據(jù)時(shí),可能會(huì)出現(xiàn)以下幾種問題:

  • 臟讀(Dirty Read):一個(gè)事務(wù)能夠讀取另一個(gè)未提交事務(wù)的數(shù)據(jù)。例如,T1 修改了一行數(shù)據(jù)但尚未提交,此時(shí) T2 讀取到了這行未提交的數(shù)據(jù);如果 T1 回滾,那么 T2 讀取到的數(shù)據(jù)就是無效的。
  • 不可重復(fù)讀(Non-repeatable Read):在同一個(gè)事務(wù)中,兩次讀取同一行數(shù)據(jù)返回不同的結(jié)果,因?yàn)樵谶@兩次讀之間,另一個(gè)事務(wù)對該行進(jìn)行了修改并提交。比如,在 T1 中第一次讀取某行后,T2 修改了該行并提交,然后 T1 再次讀取同一行時(shí)發(fā)現(xiàn)數(shù)據(jù)已改變。
  • 幻讀(Phantom Read):在一個(gè)事務(wù)中,兩次相同查詢的結(jié)果集不同,這通常是因?yàn)樵趦纱尾樵冎g有其他事務(wù)插入或刪除了滿足條件的行。例如,T1 查詢所有滿足條件 A 的記錄,之后 T2 插入了一條新的符合條件 A 的記錄并提交,再之后 T1 再次查詢相同條件 A 的記錄時(shí)會(huì)多出一條新記錄。

事務(wù)隔離級(jí)別

為了解決并發(fā)事務(wù)所引發(fā)的問題,在數(shù)據(jù)庫中引入了事務(wù)隔離級(jí)別。主要有以下幾種:

  • 讀未提交(Read Uncommitted):最低的隔離級(jí)別,它允許一個(gè)事務(wù)讀取另一個(gè)事務(wù)尚未提交的數(shù)據(jù),存在臟讀、不可重復(fù)讀、幻讀的風(fēng)險(xiǎn)。這個(gè)級(jí)別提供了最高的并發(fā)性和性能,但由于缺乏安全性,在實(shí)際應(yīng)用中很少使用。
  • 讀已提交(Read Committed):在這種隔離級(jí)別下,一個(gè)事務(wù)只能讀取到已經(jīng)提交的數(shù)據(jù),從而避免了臟讀現(xiàn)象。然而,仍然可能發(fā)生不可重復(fù)讀和幻讀,因?yàn)樵趦纱巫x之間可能有其他事務(wù)提交了更新或插入了新行。
  • 可重復(fù)讀(Repeatable Read):此級(jí)別的隔離確保在同一事務(wù)中多次讀取相同的數(shù)據(jù)將得到一致的結(jié)果,避免了不可重復(fù)讀。不過,仍然有可能出現(xiàn)幻讀問題,因?yàn)樾碌男锌梢栽趦纱巫x之間被插入或刪除。
  • 串行化(Serializable):最高級(jí)別的隔離,通過強(qiáng)制執(zhí)行嚴(yán)格的鎖機(jī)制來避免任何并發(fā)問題。在這個(gè)級(jí)別上,事務(wù)按照順序執(zhí)行,如同它們是在單線程環(huán)境中一樣,這樣就徹底避免了臟讀、不可重復(fù)讀和幻讀的可能性。然而,這也意味著更高的鎖定開銷,性能較低,一般很少使用。

下表可以更直觀的展示不同事務(wù)隔離級(jí)別所解決的問題:

隔離級(jí)別

臟讀

不可重復(fù)讀

幻讀

讀未提交(Read Uncommitted)

讀已提交(Read Committed)

×

可重復(fù)讀(Repeatable Read)

×

×

串行化(Serializable)

×

×

×

Spring 管理事務(wù)的方式

Spring 管理事務(wù)有兩種方式:編程式事務(wù)管理和聲明式事務(wù)管理。

編程式事務(wù)管理

編程式事務(wù)管理允許我們在開發(fā)時(shí),可以直接手動(dòng)的控制事務(wù)的生命周期,包括開始、提交和回滾等操作。Spring 提供了兩種主要的方式來進(jìn)行編程式事務(wù)管理:

  • TransactionTemplate:是一個(gè)模板方法的實(shí)現(xiàn)類,簡化了編程式事務(wù)管理的復(fù)雜度。TransactionTemplate 中提供了一個(gè) execute() 方法,用于包裝需要在事務(wù)上下文中執(zhí)行的操作。使用這種方式的優(yōu)點(diǎn)在于代碼更加簡潔,并且可以通過回調(diào)接口輕松處理異常情況。
// 使用 TransactionTemplate 進(jìn)行編程式事務(wù)管理
@Autowired
private TransactionTemplate transactionTemplate;

public void transferFunds(Account fromAccount, Account toAccount, BigDecimal amount) {
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            try {
                // 執(zhí)行轉(zhuǎn)賬邏輯
                fromAccount.withdraw(amount);
                toAccount.deposit(amount);
            } catch (Exception e) {
                // 如果拋出異常,事務(wù)將自動(dòng)回滾
                throw new RuntimeException("Transfer failed", e);
            }
        }
    });
}
  • PlatformTransactionManager:對于更復(fù)雜的業(yè)務(wù)場景,可以直接使用更底層的 PlatformTransactionManager 接口提供的 API 來手動(dòng)管理事務(wù)。這種方式的靈活性更高,粒度更精細(xì),但也入侵了業(yè)務(wù)代碼,增加了代碼的復(fù)雜性。PlatformTransactionManager 包含了 getTransaction()、commit() 和 rollback() 等方法,分別用來獲取事務(wù)狀態(tài)、提交事務(wù)和回滾事務(wù)。
// 使用 PlatformTransactionManager 進(jìn)行編程式事務(wù)管理
@Autowired
private PlatformTransactionManager transactionManager;

public void transferFunds() {
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

    TransactionStatus status = transactionManager.getTransaction(def);
    try {
        // 執(zhí)行復(fù)雜的業(yè)務(wù)邏輯
        // ...
        transactionManager.commit(status);
    } catch (RuntimeException e) {
        transactionManager.rollback(status);
        throw e;
    }
}

對于上述兩種編程式事務(wù)管理方案,Spring 官方更推薦使用 TransactionTemplate,因?yàn)樗庋b了大部分底層細(xì)節(jié),使得代碼更加清晰易懂。但是,對于那些需要細(xì)粒度控制事務(wù)行為的業(yè)務(wù)場景,就需要使用 PlatformTransactionManager 了。

聲明式事務(wù)管理

聲明式事務(wù)管理利用面向切面編程(AOP)來自動(dòng)管理事務(wù)邊界。這種方式的主要優(yōu)點(diǎn)在于減少了樣板代碼的數(shù)量,提高了開發(fā)效率。Spring 的聲明式事務(wù)可以通過 XML 配置文件或 @Transactional 注解來進(jìn)行配置。

  • XML 配置:早期版本的 Spring 主要依賴于 XML 文件來定義事務(wù)規(guī)則。這種方式需要維護(hù)大量額外的配置文件,增加了項(xiàng)目的復(fù)雜性。
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="serviceMethods" expression="execution(* com.example.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods"/>
</aop:config>
  • @Transactional 注解:使用 @Transactional 注解來定義事務(wù)邊界,不僅簡化了配置,而且不會(huì)污染業(yè)務(wù)代碼,使用起來更加方便,便于理解和維護(hù)。
// 還是以上述轉(zhuǎn)賬邏輯為例
@Service
public class AccountService {

    @Transactional
    public void transferFunds(Account fromAccount, Account toAccount, BigDecimal amount) {
        // 執(zhí)行轉(zhuǎn)賬邏輯
        fromAccount.withdraw(amount);
        toAccount.deposit(amount);
    }
}

通過上述對 Spring 中編程式事務(wù)與聲明式事務(wù)的對比可以看出,基于 @Transactional 注解的聲明式事務(wù)明顯更具優(yōu)勢,而且 Spring 官方也是倡導(dǎo)這種非侵入式的開發(fā)方式。以下是 Spring 官方對如何選擇這兩種事務(wù)的建議:

圖片圖片

翻譯過來大體意思是:如果應(yīng)用中的事務(wù)操作很少,編程式事務(wù)管理如使用 TransactionTemplate 可以提供更直接的控制和靈活性;如果具有多個(gè)事務(wù)操作,聲明式事務(wù)管理更為合適,因?yàn)樗渲煤唵?,可以將事?wù)管理邏輯從業(yè)務(wù)代碼中分離出來,保持代碼清晰。聲明式事務(wù)管理因其簡潔性和低侵入性而在多數(shù)情況下是更佳的選擇。

話說回來,事務(wù)多的場景下都可以使用聲明式事務(wù)管理,少的時(shí)候也用沒什么問題吧~

@Transactional 注解介紹

@Transactional 注解是 Spring 框架提供的一個(gè)用于管理事務(wù)的注解,這個(gè)注解允許我們以聲明的方式定義事務(wù)邊界,簡化事務(wù)管理的過程,它是利用 AOP 實(shí)現(xiàn)的。@Transactional 注解包含很多屬性,我們通過合理配置這些屬性,就可以在開發(fā)時(shí)精確控制事務(wù)的行為,確保應(yīng)用程序的一致性和可靠性。

首先,我們通過源碼來看下這個(gè)注解的屬性。

@Transactional 注解源碼

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

    /**
     * value 和 transactionManager 是等價(jià)的,用于指定要使用的事務(wù)管理器的名稱。
     * 在多數(shù)據(jù)源或多事務(wù)管理器的應(yīng)用場景中,可以通過這兩個(gè)屬性明確指出具體使用哪個(gè)
     * 事務(wù)管理器來管理當(dāng)前事務(wù)。
     */
    @AliasFor("transactionManager")
    String value() default "";

    // 與 value 一個(gè)意思
    @AliasFor("value")
    String transactionManager() default "";

    // 暫未使用
    String[] label() default {};

    // 該屬性定義了事務(wù)的傳播機(jī)制,默認(rèn)值是 Propagation.REQUIRED
    Propagation propagation() default Propagation.REQUIRED;

    // 指定事務(wù)的隔離級(jí)別,DEFAULT 是默認(rèn)使用底層數(shù)據(jù)庫的默認(rèn)隔離級(jí)別
    Isolation isolation() default Isolation.DEFAULT;

    /**
     * 指定事務(wù)超時(shí)時(shí)間,事務(wù)必須完成的最大秒數(shù),如果事務(wù)在規(guī)定時(shí)間內(nèi)未能完成,將會(huì)自動(dòng)回滾
     * 默認(rèn)值 -1,沒有超市限制
     */
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

    // 以字符串的形式指定超時(shí)時(shí)間
    String timeoutString() default "";

    // 標(biāo)記當(dāng)前事務(wù)是否為只讀事務(wù)
    boolean readOnly() default false;

    /**
     * 列出哪些異常類型可以導(dǎo)致事務(wù)回滾(列出的類型及其子類都會(huì)導(dǎo)致回滾)
     * 運(yùn)行時(shí)異常(RuntimeException 及其子類)和 Error 會(huì)導(dǎo)致回滾,而檢查異常不會(huì),
     * 如需要回滾,可通過該屬性指定。比如:rollbackFor = Exception.class
     * 檢查異常與非檢查異常還分不太清的同學(xué),可以去看下下邊這篇文章:
     * https://mp.weixin.qq.com/s/JMVmrhaFA0EXetmsohUt1Q?token=1081902717&lang=zh_CN
     */
    Class<? extends Throwable>[] rollbackFor() default {};

    // 與 rollbackFor 類似,是通過指定類名字符串的方式指定回滾異常類
    String[] rollbackForClassName() default {};

    // 指定哪些異常類型不應(yīng)該觸發(fā)事務(wù)回滾
    Class<? extends Throwable>[] noRollbackFor() default {};
    // 以指定類名的方式指定哪些異常類型不應(yīng)該觸發(fā)事務(wù)回滾
    String[] noRollbackForClassName() default {};
}

事務(wù)傳播行為

事務(wù)的傳播行為決定了當(dāng)方法被調(diào)用時(shí),如何處理現(xiàn)有的事務(wù)上下文或創(chuàng)建新的事務(wù)。Spring 中定義了事務(wù)的七種傳播行為:

public enum Propagation {
    /**
     * 默認(rèn)的傳播行為,如果當(dāng)前存在事務(wù),則加入該事務(wù);如果不存在,則創(chuàng)建一個(gè)新的事務(wù)。
     * 能夠確保所有相關(guān)操作都在同一個(gè)事務(wù)上下文中進(jìn)行。
     */
    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

    /**
     * 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果沒有,則以非事務(wù)方式執(zhí)行。
     * 適用于那些在有無事務(wù)環(huán)境中都可以的方法,比如說在讀取數(shù)據(jù)時(shí),通常不需要事務(wù)支持。
     */
    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

    /**
     * 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果不存在,則拋出異常。很少使用
     * 它要求必須在一個(gè)已經(jīng)存在的事務(wù)上下文中執(zhí)行,否則將拋出異常。
     */
    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

    /**
     * 總是創(chuàng)建一個(gè)新的事務(wù),即使當(dāng)前已經(jīng)存在事務(wù)也會(huì)將其掛起。
     * 通常用于需要獨(dú)立于外部事務(wù)執(zhí)行的操作,例如發(fā)送電子消息或記錄日志等非關(guān)鍵業(yè)務(wù),
     * 防止它們的失敗影響主事務(wù)的狀態(tài)。
     */
    REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

    /**
     * 以非事務(wù)方式執(zhí)行,并且如果當(dāng)前存在事務(wù),則暫停當(dāng)前事務(wù)。
     * 用于那些明確不需要事務(wù)支持的任務(wù),比如文件上傳下載等操作,即使是在事務(wù)上下文中被調(diào)用,
     * 它也會(huì)暫時(shí)停止現(xiàn)有的事務(wù),直到完成自己的任務(wù)后再恢復(fù)原來的事務(wù)狀態(tài)。
     */
    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

    /**
     * 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。
     * 與 MANDATORY 相反,NEVER 確保方法永遠(yuǎn)不會(huì)在一個(gè)事務(wù)上下文中執(zhí)行
     */
    NEVER(TransactionDefinition.PROPAGATION_NEVER),

    /**
     * 如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行;如果沒有,則創(chuàng)建一個(gè)新的事務(wù)。
     * 嵌套事務(wù)允許內(nèi)部事務(wù)獨(dú)立于外部事務(wù)進(jìn)行提交或回滾,但仍然共享相同的資源鎖定。
     */
    NESTED(TransactionDefinition.PROPAGATION_NESTED);
}

事務(wù)隔離級(jí)別

SQL 標(biāo)準(zhǔn)定義了四種不同的隔離級(jí)別來控制一個(gè)事務(wù)對另一個(gè)事務(wù)可見的數(shù)據(jù)范圍(見第一章節(jié)事務(wù)基礎(chǔ)知識(shí)中)。Spring 中也定義了與 SQL 標(biāo)準(zhǔn)相對應(yīng)的隔離級(jí)別如下:

public enum Isolation {
    // 使用數(shù)據(jù)庫默認(rèn)的隔離級(jí)別
 DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
    // 以下 4 種與 SQL 標(biāo)準(zhǔn)相對應(yīng)
    // 允許臟讀、不可重復(fù)讀和幻讀
 READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
    // 防止臟讀,但允許不可重復(fù)讀和幻讀
 READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
    // 防止臟讀和不可重復(fù)讀,但允許幻讀
 REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
    // 完全防止臟讀、不可重復(fù)讀和幻讀,提供最高級(jí)別的隔離,但可能導(dǎo)致較低的并發(fā)性能
 SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
}

Spring 為什么要自己提供一套隔離級(jí)別?

首先,不同的數(shù)據(jù)庫管理系統(tǒng)(DBMS)可能支持不同的隔離級(jí)別名稱和行為。例如,MySQL 的 InnoDB 存儲(chǔ)引擎默認(rèn)使用的是 REPEATABLE_READ,而 Oracle 和 SQL Server 默認(rèn)采用的是 READ_COMMITTED。為了給使用者提供一個(gè)統(tǒng)一的接口,Spring 定義了一套標(biāo)準(zhǔn)的隔離級(jí)別枚舉值:DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE。這樣做可以讓使用者不必關(guān)心底層數(shù)據(jù)庫的具體實(shí)現(xiàn),只需按照通用的標(biāo)準(zhǔn)來設(shè)定隔離級(jí)別即可。

@Transactional 注解的用法

還是先看下 Spring 官方文檔的介紹:

You can apply the @Transactional annotation to an interface definition, a method on an interface, a class definition, or a public method on a class.

The Spring team recommends that you annotate only concrete classes (and methods of concrete classes) with the @Transactional annotation, as opposed to annotating interfaces. You certainly can place the @Transactional annotation on an interface (or an interface method), but this works only as you would expect it to if you use interface-based proxies. The fact that Java annotations are not inherited from interfaces means that, if you use class-based proxies (proxy-target-class="true") or the weaving-based aspect (mode="aspectj"), the transaction settings are not recognized by the proxying and weaving infrastructure, and the object is not wrapped in a transactional proxy.

文檔中介紹:@Transactional 注解可以應(yīng)用于接口、接口方法、具體類或其公共方法上。且 Spring 團(tuán)隊(duì)不推薦將 @Transactional 作用于接口上,因?yàn)?Java 注解不會(huì)從接口繼承,在使用基于類的代理(cglib 代理)或 AspectJ 時(shí),接口上的事務(wù)設(shè)置將不被識(shí)別,也就會(huì)失效。所以開發(fā)中常用的方式是將其標(biāo)記在類或者類方法上:

  1. 作用于類時(shí):
  • 默認(rèn)應(yīng)用到所有公共方法:當(dāng) @Transactional 應(yīng)用于一個(gè)具體類時(shí),它將應(yīng)用于該類中的所有公共方法。該類每個(gè)被調(diào)用的公共方法都將根據(jù)注解中指定的事務(wù)屬性(如傳播行為、隔離級(jí)別等)運(yùn)行在一個(gè)事務(wù)上下文中。
  • 可以被方法級(jí)別的注解覆蓋:如果類中某個(gè)方法也有自己的 @Transactional 注解,則該方法級(jí)別的配置會(huì)覆蓋類級(jí)別的配置。
  1. 作用于方法時(shí):
  • 精確控制單個(gè)方法的事務(wù)行為:將 @Transactional 直接應(yīng)用于方法上可以更精細(xì)地控制每個(gè)方法的事務(wù)特性。這樣可以在同一個(gè)類中為不同方法設(shè)定不同的事務(wù)規(guī)則,例如不同的傳播行為、隔離級(jí)別或超時(shí)時(shí)間等。
  • 避免不必要的事務(wù)開銷:只有那些真正需要事務(wù)管理的方法才應(yīng)該標(biāo)記上 @Transactional。如果整個(gè)類都被標(biāo)記了,但并非該類的所有方法都需要事務(wù)支持,那么可能會(huì)引入不必要的性能開銷。

注意,在 Spring Boot 項(xiàng)目中,由于自動(dòng)裝配的支持,直接使用 @Transactional 注解即可啟用事務(wù)管理。相比之下,傳統(tǒng) Spring 項(xiàng)目需要顯式配置:在 applicationContext.xml 中使用 <tx:annotation-driven/> 或在 Java 配置類上添加 @EnableTransactionManagement 注解來開啟事務(wù)支持。

事務(wù)失效場景

某些情況下,雖然加上了 @Transactional 注解,但是事務(wù)仍然可能不會(huì)按照預(yù)期工作,導(dǎo)致數(shù)據(jù)不一致等問題,這里列舉一下幾種開發(fā)中常見的場景,如果遇到事務(wù)失效問題,按以下幾種情況排查基本可解決問題:

  • 訪問權(quán)限問題:Spring 的代理機(jī)制只能攔截 public 方法的調(diào)用。對于非 public 方法,代理無法對其進(jìn)行增強(qiáng),因此事務(wù)管理器不能介入這些方法的執(zhí)行過程。
@Service
public class MyService {

    @Transactional
    private void updateData() {
        // 事務(wù)不會(huì)生效
    }
}
  • 方法自調(diào)用問題:在一個(gè)類內(nèi)部一個(gè)非事務(wù)方法調(diào)用了事務(wù)方法,此時(shí)事務(wù)不會(huì)按預(yù)期生效。因?yàn)槭聞?wù)是通過 AOP 實(shí)現(xiàn)的,由于 Spring AOP 的代理機(jī)制,默認(rèn)情況下只有外部通過代理對象調(diào)用的方法才會(huì)被攔截并應(yīng)用事務(wù)管理,而內(nèi)部方法的調(diào)用是通過this 來調(diào)用的,this 指向的是代理的目標(biāo)對象,也就是原始對象,不會(huì)經(jīng)過代理,因此事務(wù)不會(huì)生效。
@Service
public class MyService {

    @Transactional
    public void methodA() {
        // 正常的事務(wù)管理
    }

    public void methodB() {
        methodA(); // 類內(nèi)部自調(diào)用,事務(wù)不會(huì)生效
    }
}
  • 吞異常:Spring 默認(rèn)只在遇到運(yùn)行時(shí)異常(RuntimeException)或錯(cuò)誤(Error)時(shí)回滾事務(wù)。如果異常被捕獲而不拋出,或者拋出了非運(yùn)行時(shí)異常而沒有在注解中指定,事務(wù)將正常提交。所以確保異常能夠傳播到方法外,或者顯式配置 rollbackFor 屬性以響應(yīng)特定類型的檢查型異常。
@Service
public class MyService {

    @Transactional
    public void processData() {
        try {
            // 數(shù)據(jù)庫操作,發(fā)生異常
        } catch (Exception e) {
            // 異常被捕獲但未處理或重新拋出
            log.error("An error occurred", e);
        }
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void processData() {
        
    }
}
  • Bean 未被 Spring 管理:如果一個(gè)類沒有被 Spring 容器管理,那么即使該類上的方法使用了 @Transactional 注解,事務(wù)也不會(huì)生效。@Transactional 的事務(wù)管理依賴于 Spring 的 AOP 代理機(jī)制,只有由 Spring 容器創(chuàng)建和管理的對象才能正確應(yīng)用這些代理。
public class MyService {
    @Transactional
    public void processData() {
        // 數(shù)據(jù)庫操作
    }
}

// 沒有通過 Spring 容器獲取 bean 對象
MyService service = new MyService();
service.processData(); // 事務(wù)不會(huì)生效
  • 異步方法:Spring 的事務(wù)管理基于當(dāng)前線程的事務(wù)上下文進(jìn)行的,而事務(wù)上下文是存儲(chǔ)在 TransactionSynchronizationManager 類中的線程局部變量(ThreadLocal)中的,因此當(dāng)一個(gè)方法上同時(shí)標(biāo)記了 @Async 和 @Transactional 注解時(shí),事務(wù)管理可能不會(huì)按預(yù)期工作,因?yàn)閷?shí)際的業(yè)務(wù)邏輯在新線程中執(zhí)行,而事務(wù)上下文不能夠正確地傳播到新線程中。所以,應(yīng)盡量避免這兩個(gè)注解同時(shí)標(biāo)記在同一個(gè)方法上,可以將事務(wù)操作單獨(dú)抽取。
@Service
public class MyService {

    @Async
    @Transactional
    public void asyncMethod() {
        // 數(shù)據(jù)庫操作
        // 事務(wù)可能不會(huì)按預(yù)期生效
    }
}

本文主要討論了 Spring 編程式和聲明式兩種管理事務(wù)的方式以及 @Transactional 注解的使用和常見問題分析,下篇我將會(huì)從源碼的角度分析 @Transactional 注解的解析(代理生成)以及事務(wù)的執(zhí)行過程。

責(zé)任編輯:武曉燕 來源: Java驛站
相關(guān)推薦

2023-11-13 18:36:04

知識(shí)抽取NER

2023-10-31 14:04:17

Rust類型編譯器

2023-06-07 14:07:00

架構(gòu)

2023-04-13 08:40:12

MySQL服務(wù)器SELECT

2023-01-03 08:13:26

GoModulesMaven

2023-03-07 07:50:15

Transactio事務(wù)代碼

2022-06-15 08:00:50

磁盤RedisRocketMQ

2024-09-13 09:05:31

架構(gòu)思維程序

2021-12-14 09:34:31

丑數(shù)順序指針

2021-05-31 09:23:04

管道模式責(zé)任鏈

2024-10-17 10:00:59

2022-07-11 09:00:37

依賴配置文件Mybati

2024-03-04 07:41:18

SpringAOPOOP?

2024-09-11 08:02:27

k8sgitlab升級(jí)

2023-05-09 07:51:28

Spring循環(huán)依賴

2023-06-27 13:47:00

分布式事務(wù)本地事務(wù)

2022-04-01 08:48:45

JavaPythonRuby

2024-08-09 08:17:07

SSH服務(wù)器架構(gòu)

2023-01-29 08:08:34

并發(fā)庫conc通用庫

2023-07-10 08:36:21

工具pptword
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)