Spring 事務傳播行爲(一)

Spring 提供了對數據庫事務的支持,除了常說的事務隔離級別,Spring 定義了不同的事務傳播行爲,用來簡化我們在應用代碼中事務在不同的方法中的傳播,在平時的開發中我們可以簡單地說,Spring 的事務傳播就是解決被調用方法如果出錯那麼調用方是否進行回滾的問題。但是長久以來,我都不是很理解這些傳播行爲究竟具體表示什麼意思,即使看過很多講述的文章,但是看過之後還是不清楚各種傳播行爲配合使用的結果是怎樣的,所以本文謹以實驗的方式來記錄一下各種傳播行爲所產生的結果,以加深對其使用的理解。
本文使用 Springboot + MyBatis,僅通過示例代碼演示各種事務傳播行爲時,方法的執行情況。

本文只試驗被調用方法中出現異常並且未被捕獲時的事務傳播情況,調用方法中出現異常的情況下次再進行試驗說明

1. 創建表

# user 表
 create table if not exists user (
    id bigint   not null auto_increment,
    name varchar(20) not null,
    sex tinyint,
    create_time datetime not null,
    primary key (id)
) engine=INNODB default charset=utf8mb4;

# 賬戶表
create table if not exists account(
    id bigint not null auto_increment,
    balance decimal(14, 2) not null default 0.00,
    user_id bigint not null,
    create_time datetime not null,
    primary key (id)
) engine = INNODB default charset=utf8mb4;

2. 搭建Springboot 項目,目錄如下

我們主要使用 UserService.java 和 AccountService.java 來演示,代碼如下:

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountMapper accountMapper;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void add(Account account) {
        accountMapper.insert(account);
        int i = 1 / 0;
    }
}

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private AccountService accountService;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void add(User user, Account account) {
        userMapper.insertUser(user);
        accountService.add(account);
    }
}

可以看到,我們在UserService.add() 方法中調用了 AccountService.add() 方法,並且我們在AccountService.add() 方法中製造一個異常,兩個方法的作用都是向對應的表中插入一條數據,下面我們使用不同的傳播行爲來進行測試,數據的關聯正確性請忽略。

  1. 調用方法使用 REQUIRED
調用方法 被調用方法 調用方法執行結果 被調用方法執行結果
required 失敗 失敗
required required 失敗 失敗
required required_new 失敗 失敗
required nested 失敗 失敗
required mandatory 失敗 失敗
required supports 失敗 失敗
required not_supports 失敗 成功
required never 失敗 失敗

required: 支持當前事務,如果被調用方法沒有事務,則將其加入到調用方的事務中;如果被調用方法是not_supported,則將其以非事務方式執行,即被調用方法不會滾;如果被調用方法標記了 never, 則表示調用方不能存在事務,否則會拋出異常,所以被調用方法不是由於 1/0 而回滾,而是因爲事務衝突的原因沒有得到執行而直接拋出了異常。

  1. 調用方法使用 SUPPORTS
調用方法 被調用方法 調用方法執行結果 被調用方法執行結果
supports 成功 成功
supports required 成功 失敗
supports required_new 成功 失敗
supports nested 成功 失敗
supports mandatory 失敗 失敗
supports supports 成功 成功
supports not_supports 成功 成功
supports never 成功 成功

supports: 支持當前事務,如果沒有事務,則以非事務方式運行。如果被調用方法有非強制的事務(required, requires_new),則被調用方法會回滾,調用方法不會回滾;如果被調用方法有強制事務(mandatory), 則調用方法會拋出事務衝突的的異常,被調用方法得不到執行,所以調用方法和被調用方法都會回滾;如果被調用方沒有事務,則調用方法和被調用方法都已非事務方式運行,不會進行回滾。

  1. 調用方法使用 MANDATORY
調用方法 被調用方法 調用方法執行結果 被調用方法執行結果
mandatory 失敗 失敗
mandatory required 失敗 失敗
mandatory required_new 失敗 失敗
mandatory nested 失敗 失敗
mandatory mandatory 失敗 失敗
mandatory supports 失敗 失敗
mandatory not_supports 失敗 失敗
mandatory never 失敗 失敗

mandatory: 支持當前事務,如果沒有事務,則拋出異常。當被調用方法標記了not_supported、 never或者沒有事務時,直接拋出事務異常,被調用方法不能得到執行,所以失敗;其他情況均以事務方式執行,所以進行了回滾。

  1. 調用方法使用 NOT_SUPPORTS
調用方法 被調用方法 調用方法執行結果 被調用方法執行結果
not_supports 成功 成功
not_supports required 成功 失敗
not_supports required_new 成功 失敗
not_supports nested 成功 失敗
not_supports mandatory 成功 失敗
not_supports supports 成功 成功
not_supports not_supports 成功 成功
not_supports never 成功 成功

not_supported: 不支持當前事務,如果當前有事務,則將其掛起,以非事務方式執行。在調用方法上標記not_supported, 則調用方法不會進行回滾,被調用方法是否回滾取決於被調用方法自身的事務機制。

  1. 調用方法使用 REQUIRES_NEW
調用方法 被調用方法 調用方法執行結果 被調用方法執行結果
requires_new 失敗 失敗
requires_new required 失敗 失敗
requires_new required_new 失敗 失敗
requires_new nested 失敗 失敗
requires_new mandatory 失敗 失敗
requires_new supports 失敗 失敗
requires_new not_supports 失敗 成功
requires_new never 失敗 失敗

requires_new: 不支持當前事務,如果沒有事務,則新建事務,如果有事務,則將當前事務掛起。如果被調用方法上標記了never,則直接拋出事務異常,被調用方法不能得到執行,所以調用方法也進行回滾; 如果被調用方法標記了not_supported, 則被調用方法以非事務方式執行,不會進行回滾,調用方法由於1/0的異常而進行回滾;如果被調用方法沒有事務,則新建事務,所以進行了回滾。

  1. 調用方法使用NEVER
調用方法 被調用方法 調用方法執行結果 被調用方法執行結果
never 成功 成功
never required 成功 失敗
never required_new 成功 失敗
never nested 成功 失敗
never mandatory 成功 失敗
never supports 成功 成功
never not_supports 成功 成功
never never 成功 成功

never: 不支持當前事務,以非事務方式運行,如果當前有事務,則拋出異常。所以,當被調用方有強制事務時,直接拋出事務衝突的異常導致沒有得到執行導致失敗;當被調用方法有required、requires_new時,因爲 1/0而進行回滾。但是如果never是被標記在被調用方式上時,如果調用方法有事務就會拋出異常。

  1. 調用方法使用NESTED
調用方法 被調用方法 調用方法執行結果 被調用方法執行結果
nested 失敗 失敗
nested required 失敗 失敗
nested required_new 失敗 失敗
nested nested 失敗 失敗
nested mandatory 失敗 失敗
nested supports 失敗 失敗
nested not_supports 失敗 成功
nested never 失敗 失敗

nested: 如果存在事務,則以嵌套事務方式執行。可以看到,在不捕獲異常的情況下,和required的方式相似,如果被調用方法標記了 nested, 並且在調用方法中try-catch了,則只有當調用方法提交後,被調用方法纔會被提交,否則被調用方法會回滾。

3. 總結

  • 什麼是當前事務?
    相對於被調用方法來說,調用方法的事務狀態就是當前事務。

  • 什麼是支持當前事務?
    被調用方法是否按照當前事務的執行狀態來運行,如果是,則表示支持當前事務。

可以看到,Spring中事務傳播行爲主要分爲兩種類型:支持當前事務(required/ mandatory/ support)、 不支持當前事務(required_new/ not_supported/ never)。

以上謹代表個人的理解,如有不當之處,敬請批評指正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章