1. 程式人生 > 其它 >SpringBoot | 1.4 資料庫事務處理

SpringBoot | 1.4 資料庫事務處理

前言

前面講解了Sring的AOP,可以知道它是用來抽取公共程式碼,增強方法的。而在JDBC操作資料庫進行資料處理時,有很多重複的公共程式碼;事務的提交與回滾跟AOP的約定流程很相似。因此,Spring資料庫事務程式設計的思想基於AOP的設計思想,資料庫事務處理是AOP的一種典型應用。


1. 事務的一些概念

首先我們要對事務常用概念有一個瞭解。

什麼事務:

  • 事務是資料庫操作最基本單元,邏輯上一組操作,要麼都成功,如果有一個失敗所有操作都失敗
  • 典型場景:銀行轉賬

資料庫事務四個特性(ACID):

  • 原子性(業務單元的操作要麼全部成功,要麼全部失敗)
  • 一致性(事務完成時,所有資料保持一致)
  • 隔離性(核心,為了壓制丟失更新的產生,處理高併發的關鍵)
  • 永續性(事務結束後,所有資料固化到一個地方,如:磁碟)

事務的操作方法:

  • 宣告式事務管理(註解方式)
  • 程式設計式事務管理(xml配置)

這裡僅討論宣告式事務管理


2. 註解宣告式事務管理

Spring AOP的約定,會將我們的程式碼織入到約定的流程中。基於AOP思想的事務處理,也有這樣一個約定,其中最重要的註解是@Transactional

@Transactional

  • 事務性的
  • 可以標註在類和方法上,推薦類上;
  • 該註解可以配置一些屬性,如:事務隔離級別、傳播行為與異常型別等。Spring IoC容器在載入時將配置資訊解析,存到事務定義器TransactionDefinition
    裡,記錄哪些類或方法需要採用什麼策略去啟動事務功能。

Spring資料庫事務約定:


具體流程:當事務啟動時,Spring會根據事務定義器內的配置設定事務。首先根據傳播行為確定事務策略;然後是隔離級別、超越時間、只讀等內容設定。直到呼叫開發者的業務程式碼,此時若沒有異常,Spring資料庫攔截器會替我們提交事務;如果發生異常,需要判斷事務定義器內配置,若事務定義器約定了該型別異常不回滾,則提交事務;若沒有配置或配置回滾,則進行事務回滾並丟擲異常。


@Transactional原始碼

從原始碼中知可以配置哪些資訊:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    //通過bean name制定事務管理器
    @AliasFor("transactionManager")
    String value() default "";

    //同value屬性
    @AliasFor("value")
    String transactionManager() default "";

    String[] label() default {};

    //制定傳播行為(重點)
    Propagation propagation() default Propagation.REQUIRED;

    //制定隔離級別(重點)
    Isolation isolation() default Isolation.DEFAULT;

    //制定超時時間(單位秒)
    int timeout() default -1;

    String timeoutString() default "";

    //是否只讀事件
    boolean readOnly() default false;

    //方法在發生指定異常時回滾,預設所有異常回滾
    Class<? extends Throwable>[] rollbackFor() default {};

    //方法在發生指定異常名稱時回滾,預設所有異常回滾
    String[] rollbackForClassName() default {};

    //方法在發生指定異常時“不”回滾,預設所有異常回滾
    Class<? extends Throwable>[] noRollbackFor() default {};

    //方法在發生指定異常名稱時“不”回滾,預設所有異常回滾
    String[] noRollbackForClassName() default {};
}

3. 隔離級別

從上面分析可知,隔離級別isolation與傳播行為propagation是@Transactional註解的兩個十分重要的配置項,因此這裡單獨拿出來講。

丟失更新:

  • 第一類丟失更新:一個事務回滾,另一個事務提交引發資料不一致。(如今資料庫系統已解決)
  • 第二類丟失更新:事務1無法知道事務2存在,按事務1提交結果。(需要設定隔離級別)

三類讀的問題:

  • 髒讀:一個事務讀取另一個事務沒有提交的資料;

  • 不可重複讀:庫存對於事務2而言是個可變化值;不可重複讀的是資料庫單一記錄值。

  • 幻讀:幻讀的資料不是資料庫儲存值,是統計值。

四類隔離級別:

用來解決上述三類讀問題,隔離級別由高到低分為:未提交讀、讀寫提交、可重複讀、序列化。

未提交讀:

  • 允許一個事務讀取另一個事務沒有提交的資料;
  • 優點:併發能力高;
  • 缺點:可能發生髒讀;
  • 是最低的隔離級別,一種危險的隔離級別。

讀寫提交:

  • 一個事務只能讀取另一個事務已經提交的資料;
  • 優點:解決髒讀問題;
  • 缺點:可能造成不可重複讀問題。
克服髒讀

可重複度:

  • 克服不可重複讀問題;
  • 優點:克服不可重複讀問題;
  • 缺點:
克服不可重複讀

序列化:

  • 要求所有SQL按順序執行;
  • 優點:保證資料一致性;
  • 缺點:效能低。

使用合理的隔離級別解決三類讀的問題:

使用隔離級別解決三類讀問題

考慮效能,在實際中會以讀寫提交為主,其能防止髒讀,不能避免不可重複讀與幻讀。為了克服資料不一致性與效能問題,可以使用樂觀鎖或使用Redis作為資料載體。

對於隔離級別,不同資料庫支援不同:Oracle支援讀寫提交和序列化,預設讀寫提交;MySQL支援4種,預設可重複讀。

修改隔離級別的方法:

  • 在@Transactional註解上配置屬性;
    @Service
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public class UserService{
    
修改隔離級別

  • 通過application.properties配置檔案配置;
  #隔離級別數字配置含義:
  #-1 資料庫預設隔離級別
  #1  未提交讀
  #2  讀寫提交
  #4  可重複讀
  #8  序列化
  #tomcat資料來源預設隔離級別
  spring.datasource.tomcat.default-transaction-isolation=2
  #dbcp2資料庫連線池預設隔離級別
  #spring.datasource.dbcp2.default-transaction-isolation=2

4. 傳播行為

傳播行為是方法間呼叫事務採取的策略問題。如在處理批量檔案時,大部分成功,小部分失敗,我們只希望那小部分失敗的回滾。

Spring在Propagation原始碼中定義了7種傳播行為:

public enum Propagation {
    /**
     * 需要事務,預設傳播行為,如果當前存在事務,就沿用當前事務,
     * 否則新建一個事務執行子方法
     */
    REQUIRED(0),

    /**
     * 支援事務,如果當前存在事務,就沿用當前事務,
     * 否則繼續採用無事務方式執行子方法
     */
    SUPPORTS(1),

    /**
     * 必須使用事務,如果當前存在事務,就沿用當前事務,
     * 如果當前沒有事務,則會丟擲異常
     */
    MANDATORY(2),

    /**
     * 無論當前事務是否存在,都會建立新事務執行方法,
     * 這樣新事務就可以擁有新的鎖和隔離級別等特性,與當前事務相互獨立
     */
    REQUIRES_NEW(3),

    /**
     * 不支援事務,當前存在事務時,將掛起事務,執行方法
     */
    NOT_SUPPORTED(4),

    /**
     * 不支援事務,如果當前存在事務時,則丟擲異常,否則繼續使用無事務機制執行
     */
    NEVER(5),

    /**
     * 在當前方法呼叫子方法時,如果子方法發生異常
     * 只回滾子方法執行過的sql,而不回滾當前方法的事務
     */
    NESTED(6);

    private final int value;

    private Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

其中,REQUIREDREQUIRES_NEWNESTED三種傳播行為最常用。


新增傳播行為方法:

@Service
@Transactional(propagation = Propagation.REQUIRED)
public class UserService{

對於NESTED而言,並不是所有資料庫支援儲存點技術,因此Spring的內部規則是:如果資料庫支援儲存點技術,就啟用儲存點技術;反之則新建一個任務去執行子方法,相當於REQUIRES_NEW

NESTEDREQUIRES_NEW的區別是:前者會沿用當前事務的隔離級別和鎖等特性,後者擁有自己的隔離級別和鎖等特性。


5. @Transactional自呼叫失效問題

一個類自身方法之間的呼叫,每次呼叫不能產生新的事務。

失效原因:
AOP原理是動態代理,而自呼叫是類自身的呼叫,不是代理物件去呼叫,就不會產生AOP,開發者程式碼無法織入到約定流程中去。


自呼叫失效問題解決:

  • 用一個service呼叫另一個service;
  • 從Spring IoC容器中使用applicationContext.getBean方法獲取代理物件。


最後

新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公眾號,會分享一些更日常的東西!
如需轉載,請標註出處!