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() 方法中製造一個異常,兩個方法的作用都是向對應的表中插入一條數據,下面我們使用不同的傳播行爲來進行測試,數據的關聯正確性請忽略。
- 調用方法使用 REQUIRED
調用方法 | 被調用方法 | 調用方法執行結果 | 被調用方法執行結果 |
---|---|---|---|
required | 無 | 失敗 | 失敗 |
required | required | 失敗 | 失敗 |
required | required_new | 失敗 | 失敗 |
required | nested | 失敗 | 失敗 |
required | mandatory | 失敗 | 失敗 |
required | supports | 失敗 | 失敗 |
required | not_supports | 失敗 | 成功 |
required | never | 失敗 | 失敗 |
required: 支持當前事務,如果被調用方法沒有事務,則將其加入到調用方的事務中;如果被調用方法是not_supported,則將其以非事務方式執行,即被調用方法不會滾;如果被調用方法標記了
never
, 則表示調用方不能存在事務,否則會拋出異常,所以被調用方法不是由於1/0
而回滾,而是因爲事務衝突的原因沒有得到執行而直接拋出了異常。
- 調用方法使用 SUPPORTS
調用方法 | 被調用方法 | 調用方法執行結果 | 被調用方法執行結果 |
---|---|---|---|
supports | 無 | 成功 | 成功 |
supports | required | 成功 | 失敗 |
supports | required_new | 成功 | 失敗 |
supports | nested | 成功 | 失敗 |
supports | mandatory | 失敗 | 失敗 |
supports | supports | 成功 | 成功 |
supports | not_supports | 成功 | 成功 |
supports | never | 成功 | 成功 |
supports: 支持當前事務,如果沒有事務,則以非事務方式運行。如果被調用方法有非強制的事務(
required, requires_new
),則被調用方法會回滾,調用方法不會回滾;如果被調用方法有強制事務(mandatory
), 則調用方法會拋出事務衝突的的異常,被調用方法得不到執行,所以調用方法和被調用方法都會回滾;如果被調用方沒有事務,則調用方法和被調用方法都已非事務方式運行,不會進行回滾。
- 調用方法使用 MANDATORY
調用方法 | 被調用方法 | 調用方法執行結果 | 被調用方法執行結果 |
---|---|---|---|
mandatory | 無 | 失敗 | 失敗 |
mandatory | required | 失敗 | 失敗 |
mandatory | required_new | 失敗 | 失敗 |
mandatory | nested | 失敗 | 失敗 |
mandatory | mandatory | 失敗 | 失敗 |
mandatory | supports | 失敗 | 失敗 |
mandatory | not_supports | 失敗 | 失敗 |
mandatory | never | 失敗 | 失敗 |
mandatory: 支持當前事務,如果沒有事務,則拋出異常。當被調用方法標記了
not_supported、 never
或者沒有事務時,直接拋出事務異常,被調用方法不能得到執行,所以失敗;其他情況均以事務方式執行,所以進行了回滾。
- 調用方法使用 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, 則調用方法不會進行回滾,被調用方法是否回滾取決於被調用方法自身的事務機制。
- 調用方法使用 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
的異常而進行回滾;如果被調用方法沒有事務,則新建事務,所以進行了回滾。
- 調用方法使用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
是被標記在被調用方式上時,如果調用方法有事務就會拋出異常。
- 調用方法使用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
)。
以上謹代表個人的理解,如有不當之處,敬請批評指正。