Spring事務傳播行為實戰
一、什麼是事務傳播行為?
事務傳播行為(propagation behavior)指的就是當一個事務方法被另一個事務方法調用時,這個事務方法應該如何運行。
例如:methodA方法調用methodB方法時,methodB是繼續在調用者methodA的事務中運行呢,還是為自己開啟一個新事務運行,這就是由methodB的事務傳播行為決定的。
Spring在TransactionDefinition接口中規定了7種類型的事務傳播行為。
事務傳播行為是Spring框架獨有的事務增強特性,這是Spring為我們提供的強大的工具箱,使用事務傳播行為可以為我們的開發工作提供許多便利。
- 支持事務的傳播
- 不支持事物的傳播
- REQUIRED:(支持事務)如果當前沒有事務,就創建一個新事務,如果當前存在事務,就加入該事務(Spring默認)
- SUPPORTS:(支持事務)如果當前存在事務,就加入該事務,如果當前不存在事務,就以非事務執行
- MANDATORY:(支持事務)如果當前存在事務,就加入該事務,如果當前不存在事務,就拋出異常
- REQUIRES_NEW:(支持事務)創建新事務,無論當前存不存在事務,都創建新事務
- NOT_SUPPORTED:(不支持事務)如果當前存在事務,就把當前事務掛起
- NEVER:(不支持事務)以非事務方式執行,如果當前存在事務,則拋出異常
- NESTED:(支持事務)如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,就創建一個新事務
**說明:**父方法插入表ks_a、子方法插入表ks_b
表結構:
CREATE TABLE `ks_a` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
`name` varchar(20) DEFAULT NULL COMMENT '姓名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='測試A';
CREATE TABLE `ks_b` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
`age` tinyint(4) DEFAULT NULL COMMENT '年齡',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='測試B';
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
}
@Transactional(propagation = Propagation.REQUIRED)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:ks_a數據插入成功,ks_b數據回滾
@Transactional
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insert(ksB);
throw new RuntimeException("主方法報錯");
}
@Transactional(propagation = Propagation.REQUIRED)
public void insert(KsB ksB) {
ksBDao.insert(ksB);
}
結果:兩表數據都回滾了
@Transactional
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
}
@Transactional(propagation = Propagation.REQUIRED)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:兩表數據都回滾了
父方法無事務,子方法開啟新事務
父方法有事務,子方法和父方法共用一個事務(無論父、子方法報錯,整體回滾)
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
}
@Transactional(propagation = Propagation.SUPPORTS)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:數據都插入成功
@Transactional
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
}
@Transactional(propagation = Propagation.SUPPORTS)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:兩表數據都回滾了
如果當前不存在事務,就以非事務執行
如果當前存在事務,就加入該事務
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
}
@Transactional(propagation = Propagation.MANDATORY)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 『mandatory』
@Transactional
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
}
@Transactional(propagation = Propagation.MANDATORY)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:兩表數據都回滾了
如果當前不存在事務,就拋出異常
如果當前存在事務,就加入該事務
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:ks_a數據插入成功,ks_b數據回滾
@Transactional
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insert(ksB);
throw new RuntimeException("父方法報錯");
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insert(KsB ksB) {
ksBDao.insert(ksB);
}
結果:ks_a數據回滾,ks_b數據插入成功
無論當前存不存在事務,都創建新事務
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:數據都插入成功
@Transactional
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:ks_a數據回滾,ks_b數據插入成功
以非事務方式執行,如果當前存在事務,父方法以事務方式執行,子方法以非事務方式執行
@Transactional
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insert(ksB);
}
@Transactional(propagation = Propagation.NEVER)
public void insert(KsB ksB) {
ksBDao.insert(ksB);
}
結果:org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 『never』
以非事務方式執行,如果當前存在事務,則拋出異常
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
}
@Transactional(propagation = Propagation.NESTED)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:ks_a數據插入成功,ks_b數據回滾
@Transactional
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
}
@Override
@Transactional(propagation = Propagation.NESTED)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:數據都回滾
@Transactional
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insert(ksB);
throw new RuntimeException("主方法報錯");
}
@Transactional(propagation = Propagation.NESTED)
public void insert(KsB ksB) {
ksBDao.insert(ksB);
}
結果:數據都回滾
@Transactional
public void add() {
KsA ksA = new KsA();
ksA.setName("林");
ksAService.insert(ksA);
try {
KsB ksB = new KsB();
ksB.setAge(10);
ksBService.insertError(ksB);
} catch (Exception e) {
//dosomething
}
}
@Transactional(propagation = Propagation.NESTED)
public void insertError(KsB ksB) {
ksBDao.insert(ksB);
throw new RuntimeException("子方法報錯");
}
結果:ks_a數據插入成功,ks_b數據回滾
如果當前沒有事務,則新開事務執行
如果當前存在事務,則在嵌套事務內執行
區別在於:如果當前存在事務,子方法拋異常時
NESTED在父方法可以選擇捕獲子方法,父方法數據不會回滾;
REQUIRES無論捕不捕獲,父方法數據都回滾
區別:如果當前存在事務,父方法拋異常時
NESTED數據回滾,REQUIRES也是如此
REQUIRES_NEW數據不回滾
說明:加入該事務,指的是父、子方法共用一個事務(無論父、子方法報錯,整體回滾)
父方法無事務,子方法開啟新事務
父方法有事務,就加入該事務
如果當前不存在事務,就以非事務執行
如果當前存在事務,就加入該事務
如果當前不存在事務,就拋出異常
如果當前存在事務,就加入該事務
無論當前存不存在事務,都創建新事務
以非事務方式執行,如果當前存在事務,父方法以事務方式執行,子方法以非事務方式執行
以非事務方式執行,如果當前存在事務,則拋出異常
如果當前沒有事務,則新開事務執行
如果當前存在事務,則在嵌套事務內執行