Spring 中的事務傳播

概述

Spring中, Java方法的事務傳播類型經過 @Transactional 註解進行指明, 並經過該註解的 propagation 屬性指明事務傳播的具體類型.

@Transactional 註解的使用很是靈活, 能夠註解在服務接口上, 也能夠註解在服務類的方法上, 還能夠註解在Spring Repository的接口方法上.html

@Transactional(propagation = Propagation.REQUIRED)

各類傳播類型的說明

Spring 中一共有七中事務傳播類型. 分別說明以下:

一. Propagation.REQUIRED

若是當前已經存在事務, 那麼加入該事務, 若是不存在事務, 建立一個事務, 而後執行事務操做. 這是默認的傳播屬性值. 也是最多見的選擇.

何謂當前?

當前也就是你所聲明的服務方法被調用的時候.java

何謂已經存在事務?

也就是說在聲明的事務方法被調用的時候, 就已經在一個事務當中了. 好比下面的僞代碼:數據庫

@Transactional
public void service(){
    serviceA();
    serviceB();
}
 
@Transactional
serviceA();
@Transactional
serviceB();

service() 方法開啓了一個事務, 當 serviceA(); serviceB(); 被調用的額時候回加入 service() 所在的事務上下文. 分佈式

注意: @Transactional 沒有指明 propagation 屬性, 取默認值 Propagation.REQUIRED.net

含義爲: 要求的, 必須的. 若是被註解的方法有這個傳播屬性, 它的行爲是:代理

  1. 若是它做爲一個子事務方法, 在其餘事務方法中被調用, 那麼該方法不會建立新的事務, 使用現有的父級別的事務.
  2. 若是它做爲一個子事務方法, 沒有在其餘事務方法中被調用, 而是在非事務方法中直接調用, 那麼它會建立一個新的事務來執行數據庫操做.
概括爲: 有就用, 沒有就建立(事務).

也就能夠理解 Propagation.REQUIRED 的意思了 -- 要求的. 無論怎樣它可以保證操做老是在一個事務中進行的.日誌

應用場景: 不知道方法的調用者是否建立了事務, 可是要求當前被調用的方法必須在一個事務當中執行.code

Propagation.MANDATORY

支持當前事務,若是當前沒有事務,就拋出異常

對於這個類型, 它通常做爲一個事務的一部分定義. 不能獨立執行. 通常做爲一個事務中的子操做. 好比經典的轉帳做爲例子.orm

兩個帳戶 AB 之間轉帳, 從 A1000B , 拆分爲兩個操做, 而且要在一個事務中執行.htm

操做1: 先把 1000 加到 B 帳戶上, 若是 B 帳戶增長成功, 執行操做2
操做2: 若是 B 帳戶增長成功, 那麼 A 帳戶扣除 1000.

當兩個操做同時成功時, 事務執行成功, 不然, 任意一步錯誤, 回滾以前的所有操做. 那麼對應的操做1和操做2咱們能夠實現爲兩個 Propagation.MANDATORY 類型的Java方法. 而且把這兩個方法放在一個 Propagation.REQUIRED 類型的方法中, 例如:

// 服務接口

interface AccountService {
    TransactionLog transfer(Long accountA, Long accountB, BigDecimal amount);
}

// 服務實現

@Service
class AccountServiceImpl implements AccountService {
    // 父事務, 用於組織多個子事務.
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public TransactionLog transfer(Long accountA, Long accountB, BigDecimal amount) {
        // 先加後減
        addAmount(accountB, amount)
        addAmount(accountA, amount.negate())
    }
}

// 數據庫訪問對象

@Repository
class interface AccountRepository extends JpaRepository<Account, Long> {
        // 餘額操做(自增, 自減)
    @Modifying
    @Transactional(propagation = Propagation.MANDATORY)
    @Query(value = "UPDATE account a SET a.balance = a.balance + ?2 WHERE a.id = ?1")
    void addAmount(Long id, BigInteger amount);
}

Propagation.REQUIRES_NEW

新建事務,若是當前存在事務,把當前事務掛起
特徵: 啓動一個新的, 不依賴於環境的 "內部" 事務. 這個事務將被徹底 提交回滾 而不依賴於外部事務,它擁有本身的隔離範圍, 本身的鎖, 等等.當內部事務開始執行時, 外部事務將被掛起, 內務事務結束時, 外部事務將繼續執行. Propagation.REQUIRES_NEW 經常使用於日誌記錄,或者交易失敗仍須要留痕.

還有就是 時序控制, 支付依賴於已經建立的訂單, 無訂單不支付. 先要有訂單才能支付. 常見於事務步驟 要求時序 的狀況.

開啓一個全新的事務, 通常用於分佈式事務中的一個前期步驟. 好比一鍵購功能. 主要步驟包括:

下面的僞代碼模擬了一個外層事務和內層事務的業務過程.

// 一鍵購服務
class BuyService {
    @Transactional(propagation = Propagation.REQUIRED)
    public void buyDirectly() {
        Order OrderService.createOrder(OrderDto orderDto);
        PayService.pay(PayDto payDto);
    }
}
interface OrderService {
    void createOrder(OrderDto orderDto);
}
interface PayService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    Payment pay(PayDto payDto);
}

OrderService.createOrder 運行在外層事務中, 若是建立訂單失敗, 就不必發起支付了, 直接回滾了, 根本就到不了支付這一步.

當綁定的銀行卡餘額不足的狀況, PayService.pay(); 是能夠回滾的, 而不會影響 buyDirectly 整個事務, OrderService.createOrder(); 成功. 向銀行卡不足金額後, 能夠從新發起支付, 完成購買過程.

注意: Propagation.REQUIRES_NEW 若是做爲一個子事務運行, 調用者和被調這不要在同一個服務類中(由於Spring AOP動態代理的限制, 在同一個類中事務是不起做用的)

Propagation.REQUIRES_NEW 的通常使用場景是做爲內層事務能夠單獨回滾. 而不是回滾整個外層事務. 所以若是調用者和被調用者若是在一個類中, Propagation.REQUIRES_NEW 註解的方法並 不會 開啓一個新的事務. 所以就達不到內層事務單獨回滾的目的.

概括: 內層事務能夠獨立回滾, 不影響外層事務.
前提是外層事務的方法不能和內層事務的方法在同一個服務類中

Propagation.NOT_SUPPORTED

以非事務方式執行操做, 若是當前存在事務, 就把當前事務掛起

Propagation.NEVER

若是當前存在事務, 則拋出異常, 不然在無事務環境上執行代碼

對於方法中只存在只讀操做, 咱們可使用這個. 通常事務註解爲:

@Transactional(propagation = Propagation.NEVER, readOnly = true)

Propagation.NESTED

嵌套事務, 它的做用至關於 Propagation.REQUIRED 和 Propagation.REQUIRES_NEW 的合體

特徵: Propagation.NESTED 啓動一個 "嵌套的" 事務, 它是已經存在事務的一個真正的子事務. 潛套事務開始執行時, 它將取得一個存儲點(Savepoint). 若是這個嵌套事務失敗, 咱們將回滾到此 存儲點. 潛套事務是外部事務的一部分, 只有外部事務結束後它纔會被提交. 因而可知, Propagation.REQUIRES_NEWPropagation.NESTED 的最大區別在於, Propagation.REQUIRES_NEW 徹底是一個新的事務, 而 Propagation.NESTED 則是外部事務的子事務, 若是外部事務提交, 嵌套事務也會被,這個規則一樣適用於回滾.

注意: 使用此種事務傳播類型, 須要設置事務管理器的 nestedTransactionAllowed 屬性爲 true

/**
 * TransactionConfig.java
 *
 * 事務配置
 */
@Configuration
@EnableTransactionManagement
public class Transaction {
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory);
        transactionManager.setNestedTransactionAllowed(true);
        return transactionManager;
    }
}

Propagation.SUPPORTS

表示當前方法沒必要須要具備一個事務上下文, 可是若是有一個事務的話, 它也能夠在這個事務中運行

概括: 有沒有父級事務(外層事務)均可以接受

這個類型通常用於不會修改(UPDATE, DELETE)數據庫狀態的Java方法裏面. 若是說在上述 AccountServiceImpl 實現類的 transfer 方法中還要經過調用其餘的方法(包含SELECT語句)去返回某些數據,那麼該方法能夠標註爲 Propagation.SUPPORTS 類型.

總結

關鍵字: 建立, 掛起, 恢復, 非事務執行, 事務執行.

@Transactional(propagation=Propagation.REQUIRED)
若是有事務, 那麼加入事務, 沒有的話新建一個(默認狀況下)

@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不爲這個方法開啓事務

@Transactional(propagation=Propagation.REQUIRES_NEW)
不論是否存在事務,都建立一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務

@Transactional(propagation=Propagation.MANDATORY)
必須在一個已有的事務中執行,不然拋出異常

@Transactional(propagation=Propagation.NEVER)
必須在一個沒有的事務中執行,不然拋出異常(與Propagation.MANDATORY相反)

@Transactional(propagation=Propagation.SUPPORTS)
若是其餘bean調用這個方法,在其餘bean中聲明事務,那就用事務.若是其餘bean沒有聲明事務,那就不用事務.

參考資料

相關文章
相關標籤/搜索