我們一起聊聊 Spring 是如何管理事務(wù)的,你學(xué)會了嗎?
本篇我們事務(wù)中的基本概念開始,討論下在 Spring 框架中是如何實現(xiàn)事務(wù)管理的,并重點講解基于 @Transactional 注解的聲明式事務(wù)的實際應(yīng)用。本文僅涉及概念性的知識,原理解析將會另起一篇單獨進行介紹。
事務(wù)的基礎(chǔ)知識
事務(wù)(Transaction)是數(shù)據(jù)庫管理系統(tǒng)中一組邏輯操作的集合,這些操作作為一個整體被對待,不可拆分,以保證數(shù)據(jù)的一致性和完整性。事務(wù)具有四個關(guān)鍵屬性,通常被稱為 ACID 特性。
ACID 特性
ACID 指的是事務(wù)的四個基本特性:
- 原子性(Atomicity):一個事務(wù)中的所有操作要么全部完成,要么完全不執(zhí)行。如果事務(wù)的一部分失敗,則整個事務(wù)都會被回滾。例如,在銀行轉(zhuǎn)賬的場景中,從賬戶 A 轉(zhuǎn)賬到賬戶 B 需要兩個步驟:減少 A 的余額和增加 B 的余額。這兩個步驟必須作為一個整體成功或失敗,否則可能會導(dǎo)致資金丟失或重復(fù)。
 - 一致性(Consistency):事務(wù)必須保證數(shù)據(jù)庫從一個一致狀態(tài)轉(zhuǎn)換到另一個一致狀態(tài),即事務(wù)執(zhí)行前后數(shù)據(jù)完整性約束沒有被破壞。還是以銀行轉(zhuǎn)賬為例,一致性確保在轉(zhuǎn)賬前后,兩個賬戶的總金額保持不變,即使發(fā)生故障也不會影響這一原則。
 - 隔離性(Isolation):并發(fā)執(zhí)行的多個事務(wù)之間不會互相干擾。每個事務(wù)都應(yīng)該獨立地運行,就好像它是系統(tǒng)中唯一存在的事務(wù)一樣。假設(shè)同時有兩個轉(zhuǎn)賬請求,一個是 A 向 B 轉(zhuǎn)賬,另一個是 C 向 D 轉(zhuǎn)賬。這兩個事務(wù)應(yīng)該互不影響,它們的結(jié)果應(yīng)該是各自獨立且正確的。
 - 持久性(Durability):一旦事務(wù)提交,它對數(shù)據(jù)庫所做的更改就是永久性的,即使系統(tǒng)發(fā)生故障也不會丟失這些更改。比如,當(dāng)銀行轉(zhuǎn)賬完成后,即使服務(wù)器突然斷電,轉(zhuǎn)賬記錄也應(yīng)當(dāng)保存下來,確保用戶的資金變動信息不會丟失。
 
其他特性
除了 ACID 特性之外,還有以下幾個重要的事務(wù)屬性:
- 事務(wù)回滾(Rollback):當(dāng)事務(wù)遇到錯誤或異常時,可以撤銷所有已經(jīng)執(zhí)行的操作,使數(shù)據(jù)庫恢復(fù)到事務(wù)開始前的狀態(tài)。這是保證事務(wù)原子性和一致性的關(guān)鍵手段。例如,在一個復(fù)雜的業(yè)務(wù)流程中,如果其中一步驟失敗,整個事務(wù)將被回滾,確保之前的所有變更都取消,從而維持系統(tǒng)的穩(wěn)定狀態(tài)。
 - 事務(wù)超時(Timeout):為事務(wù)設(shè)定一個最大允許執(zhí)行時間,超過這個時間則自動終止事務(wù),以防止長時間占用資源。這對于避免死鎖和提高系統(tǒng)響應(yīng)速度非常重要。例如,在高并發(fā)環(huán)境中,某些長時間運行的事務(wù)可能導(dǎo)致資源鎖定,影響其他事務(wù)的正常進行。通過設(shè)置合理的超時值,可以及時釋放資源,保證系統(tǒng)的流暢運行。
 - 只讀事務(wù)(Read-only Transactions):某些場景下,事務(wù)只需要讀取數(shù)據(jù)而不需要修改,這時可以聲明事務(wù)為只讀模式以優(yōu)化性能。只讀事務(wù)告訴數(shù)據(jù)庫引擎當(dāng)前事務(wù)不會修改任何數(shù)據(jù),因此它可以采用更高效的查詢策略,如跳過某些類型的鎖檢查。這不僅提高了查詢的速度,還減少了對共享資源的競爭壓力。
 
并發(fā)事務(wù)中存在的問題
當(dāng)多個事務(wù)同時訪問同一份數(shù)據(jù)時,可能會出現(xiàn)以下幾種問題:
- 臟讀(Dirty Read):一個事務(wù)能夠讀取另一個未提交事務(wù)的數(shù)據(jù)。例如,T1 修改了一行數(shù)據(jù)但尚未提交,此時 T2 讀取到了這行未提交的數(shù)據(jù);如果 T1 回滾,那么 T2 讀取到的數(shù)據(jù)就是無效的。
 - 不可重復(fù)讀(Non-repeatable Read):在同一個事務(wù)中,兩次讀取同一行數(shù)據(jù)返回不同的結(jié)果,因為在這兩次讀之間,另一個事務(wù)對該行進行了修改并提交。比如,在 T1 中第一次讀取某行后,T2 修改了該行并提交,然后 T1 再次讀取同一行時發(fā)現(xiàn)數(shù)據(jù)已改變。
 - 幻讀(Phantom Read):在一個事務(wù)中,兩次相同查詢的結(jié)果集不同,這通常是因為在兩次查詢之間有其他事務(wù)插入或刪除了滿足條件的行。例如,T1 查詢所有滿足條件 A 的記錄,之后 T2 插入了一條新的符合條件 A 的記錄并提交,再之后 T1 再次查詢相同條件 A 的記錄時會多出一條新記錄。
 
事務(wù)隔離級別
為了解決并發(fā)事務(wù)所引發(fā)的問題,在數(shù)據(jù)庫中引入了事務(wù)隔離級別。主要有以下幾種:
- 讀未提交(Read Uncommitted):最低的隔離級別,它允許一個事務(wù)讀取另一個事務(wù)尚未提交的數(shù)據(jù),存在臟讀、不可重復(fù)讀、幻讀的風(fēng)險。這個級別提供了最高的并發(fā)性和性能,但由于缺乏安全性,在實際應(yīng)用中很少使用。
 - 讀已提交(Read Committed):在這種隔離級別下,一個事務(wù)只能讀取到已經(jīng)提交的數(shù)據(jù),從而避免了臟讀現(xiàn)象。然而,仍然可能發(fā)生不可重復(fù)讀和幻讀,因為在兩次讀之間可能有其他事務(wù)提交了更新或插入了新行。
 - 可重復(fù)讀(Repeatable Read):此級別的隔離確保在同一事務(wù)中多次讀取相同的數(shù)據(jù)將得到一致的結(jié)果,避免了不可重復(fù)讀。不過,仍然有可能出現(xiàn)幻讀問題,因為新的行可以在兩次讀之間被插入或刪除。
 - 串行化(Serializable):最高級別的隔離,通過強制執(zhí)行嚴(yán)格的鎖機制來避免任何并發(fā)問題。在這個級別上,事務(wù)按照順序執(zhí)行,如同它們是在單線程環(huán)境中一樣,這樣就徹底避免了臟讀、不可重復(fù)讀和幻讀的可能性。然而,這也意味著更高的鎖定開銷,性能較低,一般很少使用。
 
下表可以更直觀的展示不同事務(wù)隔離級別所解決的問題:
隔離級別  | 臟讀  | 不可重復(fù)讀  | 幻讀  | 
讀未提交(Read Uncommitted)  | √  | √  | √  | 
讀已提交(Read Committed)  | ×  | √  | √  | 
可重復(fù)讀(Repeatable Read)  | ×  | ×  | √  | 
串行化(Serializable)  | ×  | ×  | ×  | 
Spring 管理事務(wù)的方式
Spring 管理事務(wù)有兩種方式:編程式事務(wù)管理和聲明式事務(wù)管理。
編程式事務(wù)管理
編程式事務(wù)管理允許我們在開發(fā)時,可以直接手動的控制事務(wù)的生命周期,包括開始、提交和回滾等操作。Spring 提供了兩種主要的方式來進行編程式事務(wù)管理:
- TransactionTemplate:是一個模板方法的實現(xiàn)類,簡化了編程式事務(wù)管理的復(fù)雜度。TransactionTemplate 中提供了一個 execute() 方法,用于包裝需要在事務(wù)上下文中執(zhí)行的操作。使用這種方式的優(yōu)點在于代碼更加簡潔,并且可以通過回調(diào)接口輕松處理異常情況。
 
// 使用 TransactionTemplate 進行編程式事務(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ù)將自動回滾
                throw new RuntimeException("Transfer failed", e);
            }
        }
    });
}- PlatformTransactionManager:對于更復(fù)雜的業(yè)務(wù)場景,可以直接使用更底層的 PlatformTransactionManager 接口提供的 API 來手動管理事務(wù)。這種方式的靈活性更高,粒度更精細(xì),但也入侵了業(yè)務(wù)代碼,增加了代碼的復(fù)雜性。PlatformTransactionManager 包含了 getTransaction()、commit() 和 rollback() 等方法,分別用來獲取事務(wù)狀態(tài)、提交事務(wù)和回滾事務(wù)。
 
// 使用 PlatformTransactionManager 進行編程式事務(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,因為它封裝了大部分底層細(xì)節(jié),使得代碼更加清晰易懂。但是,對于那些需要細(xì)粒度控制事務(wù)行為的業(yè)務(wù)場景,就需要使用 PlatformTransactionManager 了。
聲明式事務(wù)管理
聲明式事務(wù)管理利用面向切面編程(AOP)來自動管理事務(wù)邊界。這種方式的主要優(yōu)點在于減少了樣板代碼的數(shù)量,提高了開發(fā)效率。Spring 的聲明式事務(wù)可以通過 XML 配置文件或 @Transactional 注解來進行配置。
- XML 配置:早期版本的 Spring 主要依賴于 XML 文件來定義事務(wù)規(guī)則。這種方式需要維護大量額外的配置文件,增加了項目的復(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ù)邊界,不僅簡化了配置,而且不會污染業(yè)務(wù)代碼,使用起來更加方便,便于理解和維護。
 
// 還是以上述轉(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 可以提供更直接的控制和靈活性;如果具有多個事務(wù)操作,聲明式事務(wù)管理更為合適,因為它配置簡單,可以將事務(wù)管理邏輯從業(yè)務(wù)代碼中分離出來,保持代碼清晰。聲明式事務(wù)管理因其簡潔性和低侵入性而在多數(shù)情況下是更佳的選擇。
話說回來,事務(wù)多的場景下都可以使用聲明式事務(wù)管理,少的時候也用沒什么問題吧~
@Transactional 注解介紹
@Transactional 注解是 Spring 框架提供的一個用于管理事務(wù)的注解,這個注解允許我們以聲明的方式定義事務(wù)邊界,簡化事務(wù)管理的過程,它是利用 AOP 實現(xiàn)的。@Transactional 注解包含很多屬性,我們通過合理配置這些屬性,就可以在開發(fā)時精確控制事務(wù)的行為,確保應(yīng)用程序的一致性和可靠性。
首先,我們通過源碼來看下這個注解的屬性。
@Transactional 注解源碼
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    /**
     * value 和 transactionManager 是等價的,用于指定要使用的事務(wù)管理器的名稱。
     * 在多數(shù)據(jù)源或多事務(wù)管理器的應(yīng)用場景中,可以通過這兩個屬性明確指出具體使用哪個
     * 事務(wù)管理器來管理當(dāng)前事務(wù)。
     */
    @AliasFor("transactionManager")
    String value() default "";
    // 與 value 一個意思
    @AliasFor("value")
    String transactionManager() default "";
    // 暫未使用
    String[] label() default {};
    // 該屬性定義了事務(wù)的傳播機制,默認(rèn)值是 Propagation.REQUIRED
    Propagation propagation() default Propagation.REQUIRED;
    // 指定事務(wù)的隔離級別,DEFAULT 是默認(rèn)使用底層數(shù)據(jù)庫的默認(rèn)隔離級別
    Isolation isolation() default Isolation.DEFAULT;
    /**
     * 指定事務(wù)超時時間,事務(wù)必須完成的最大秒數(shù),如果事務(wù)在規(guī)定時間內(nèi)未能完成,將會自動回滾
     * 默認(rèn)值 -1,沒有超市限制
     */
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
    // 以字符串的形式指定超時時間
    String timeoutString() default "";
    // 標(biāo)記當(dāng)前事務(wù)是否為只讀事務(wù)
    boolean readOnly() default false;
    /**
     * 列出哪些異常類型可以導(dǎo)致事務(wù)回滾(列出的類型及其子類都會導(dǎo)致回滾)
     * 運行時異常(RuntimeException 及其子類)和 Error 會導(dǎo)致回滾,而檢查異常不會,
     * 如需要回滾,可通過該屬性指定。比如: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)用時,如何處理現(xiàn)有的事務(wù)上下文或創(chuàng)建新的事務(wù)。Spring 中定義了事務(wù)的七種傳播行為:
public enum Propagation {
    /**
     * 默認(rèn)的傳播行為,如果當(dāng)前存在事務(wù),則加入該事務(wù);如果不存在,則創(chuàng)建一個新的事務(wù)。
     * 能夠確保所有相關(guān)操作都在同一個事務(wù)上下文中進行。
     */
    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
    /**
     * 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果沒有,則以非事務(wù)方式執(zhí)行。
     * 適用于那些在有無事務(wù)環(huán)境中都可以的方法,比如說在讀取數(shù)據(jù)時,通常不需要事務(wù)支持。
     */
    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
    /**
     * 如果當(dāng)前存在事務(wù),則加入該事務(wù);如果不存在,則拋出異常。很少使用
     * 它要求必須在一個已經(jīng)存在的事務(wù)上下文中執(zhí)行,否則將拋出異常。
     */
    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
    /**
     * 總是創(chuàng)建一個新的事務(wù),即使當(dāng)前已經(jīng)存在事務(wù)也會將其掛起。
     * 通常用于需要獨立于外部事務(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)用,
     * 它也會暫時停止現(xiàn)有的事務(wù),直到完成自己的任務(wù)后再恢復(fù)原來的事務(wù)狀態(tài)。
     */
    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
    /**
     * 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。
     * 與 MANDATORY 相反,NEVER 確保方法永遠(yuǎn)不會在一個事務(wù)上下文中執(zhí)行
     */
    NEVER(TransactionDefinition.PROPAGATION_NEVER),
    /**
     * 如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行;如果沒有,則創(chuàng)建一個新的事務(wù)。
     * 嵌套事務(wù)允許內(nèi)部事務(wù)獨立于外部事務(wù)進行提交或回滾,但仍然共享相同的資源鎖定。
     */
    NESTED(TransactionDefinition.PROPAGATION_NESTED);
}事務(wù)隔離級別
SQL 標(biāo)準(zhǔn)定義了四種不同的隔離級別來控制一個事務(wù)對另一個事務(wù)可見的數(shù)據(jù)范圍(見第一章節(jié)事務(wù)基礎(chǔ)知識中)。Spring 中也定義了與 SQL 標(biāo)準(zhǔn)相對應(yīng)的隔離級別如下:
public enum Isolation {
    // 使用數(shù)據(jù)庫默認(rèn)的隔離級別
 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ù)讀和幻讀,提供最高級別的隔離,但可能導(dǎo)致較低的并發(fā)性能
 SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
}Spring 為什么要自己提供一套隔離級別?
首先,不同的數(shù)據(jù)庫管理系統(tǒng)(DBMS)可能支持不同的隔離級別名稱和行為。例如,MySQL 的 InnoDB 存儲引擎默認(rèn)使用的是 REPEATABLE_READ,而 Oracle 和 SQL Server 默認(rèn)采用的是 READ_COMMITTED。為了給使用者提供一個統(tǒng)一的接口,Spring 定義了一套標(biāo)準(zhǔn)的隔離級別枚舉值:DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE。這樣做可以讓使用者不必關(guān)心底層數(shù)據(jù)庫的具體實現(xiàn),只需按照通用的標(biāo)準(zhǔn)來設(shè)定隔離級別即可。
@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 團隊不推薦將 @Transactional 作用于接口上,因為 Java 注解不會從接口繼承,在使用基于類的代理(cglib 代理)或 AspectJ 時,接口上的事務(wù)設(shè)置將不被識別,也就會失效。所以開發(fā)中常用的方式是將其標(biāo)記在類或者類方法上:
- 作用于類時:
 
- 默認(rèn)應(yīng)用到所有公共方法:當(dāng) @Transactional 應(yīng)用于一個具體類時,它將應(yīng)用于該類中的所有公共方法。該類每個被調(diào)用的公共方法都將根據(jù)注解中指定的事務(wù)屬性(如傳播行為、隔離級別等)運行在一個事務(wù)上下文中。
 - 可以被方法級別的注解覆蓋:如果類中某個方法也有自己的 @Transactional 注解,則該方法級別的配置會覆蓋類級別的配置。
 
- 作用于方法時:
 
- 精確控制單個方法的事務(wù)行為:將 @Transactional 直接應(yīng)用于方法上可以更精細(xì)地控制每個方法的事務(wù)特性。這樣可以在同一個類中為不同方法設(shè)定不同的事務(wù)規(guī)則,例如不同的傳播行為、隔離級別或超時時間等。
 - 避免不必要的事務(wù)開銷:只有那些真正需要事務(wù)管理的方法才應(yīng)該標(biāo)記上 @Transactional。如果整個類都被標(biāo)記了,但并非該類的所有方法都需要事務(wù)支持,那么可能會引入不必要的性能開銷。
 
注意,在 Spring Boot 項目中,由于自動裝配的支持,直接使用 @Transactional 注解即可啟用事務(wù)管理。相比之下,傳統(tǒng) Spring 項目需要顯式配置:在 applicationContext.xml 中使用 <tx:annotation-driven/> 或在 Java 配置類上添加 @EnableTransactionManagement 注解來開啟事務(wù)支持。
事務(wù)失效場景
某些情況下,雖然加上了 @Transactional 注解,但是事務(wù)仍然可能不會按照預(yù)期工作,導(dǎo)致數(shù)據(jù)不一致等問題,這里列舉一下幾種開發(fā)中常見的場景,如果遇到事務(wù)失效問題,按以下幾種情況排查基本可解決問題:
- 訪問權(quán)限問題:Spring 的代理機制只能攔截 public 方法的調(diào)用。對于非 public 方法,代理無法對其進行增強,因此事務(wù)管理器不能介入這些方法的執(zhí)行過程。
 
@Service
public class MyService {
    @Transactional
    private void updateData() {
        // 事務(wù)不會生效
    }
}- 方法自調(diào)用問題:在一個類內(nèi)部一個非事務(wù)方法調(diào)用了事務(wù)方法,此時事務(wù)不會按預(yù)期生效。因為事務(wù)是通過 AOP 實現(xiàn)的,由于 Spring AOP 的代理機制,默認(rèn)情況下只有外部通過代理對象調(diào)用的方法才會被攔截并應(yīng)用事務(wù)管理,而內(nèi)部方法的調(diào)用是通過this 來調(diào)用的,this 指向的是代理的目標(biāo)對象,也就是原始對象,不會經(jīng)過代理,因此事務(wù)不會生效。
 
@Service
public class MyService {
    @Transactional
    public void methodA() {
        // 正常的事務(wù)管理
    }
    public void methodB() {
        methodA(); // 類內(nèi)部自調(diào)用,事務(wù)不會生效
    }
}- 吞異常:Spring 默認(rèn)只在遇到運行時異常(RuntimeException)或錯誤(Error)時回滾事務(wù)。如果異常被捕獲而不拋出,或者拋出了非運行時異常而沒有在注解中指定,事務(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 管理:如果一個類沒有被 Spring 容器管理,那么即使該類上的方法使用了 @Transactional 注解,事務(wù)也不會生效。@Transactional 的事務(wù)管理依賴于 Spring 的 AOP 代理機制,只有由 Spring 容器創(chuàng)建和管理的對象才能正確應(yīng)用這些代理。
 
public class MyService {
    @Transactional
    public void processData() {
        // 數(shù)據(jù)庫操作
    }
}
// 沒有通過 Spring 容器獲取 bean 對象
MyService service = new MyService();
service.processData(); // 事務(wù)不會生效- 異步方法:Spring 的事務(wù)管理基于當(dāng)前線程的事務(wù)上下文進行的,而事務(wù)上下文是存儲在 TransactionSynchronizationManager 類中的線程局部變量(ThreadLocal)中的,因此當(dāng)一個方法上同時標(biāo)記了 @Async 和 @Transactional 注解時,事務(wù)管理可能不會按預(yù)期工作,因為實際的業(yè)務(wù)邏輯在新線程中執(zhí)行,而事務(wù)上下文不能夠正確地傳播到新線程中。所以,應(yīng)盡量避免這兩個注解同時標(biāo)記在同一個方法上,可以將事務(wù)操作單獨抽取。
 
@Service
public class MyService {
    @Async
    @Transactional
    public void asyncMethod() {
        // 數(shù)據(jù)庫操作
        // 事務(wù)可能不會按預(yù)期生效
    }
}本文主要討論了 Spring 編程式和聲明式兩種管理事務(wù)的方式以及 @Transactional 注解的使用和常見問題分析,下篇我將會從源碼的角度分析 @Transactional 注解的解析(代理生成)以及事務(wù)的執(zhí)行過程。















 
 
 

















 
 
 
 