Spring事務傳播行為實戰

2022-10-28 18:00:24

一、什麼是事務傳播行為?

事務傳播行為(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';

1、REQUIRED

1.1、父方法無事務,子方法開啟事務,子方法報錯

	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資料回滾
在這裡插入圖片描述
在這裡插入圖片描述

1.2、父方法開啟事務,子方法開啟事務,父方法報錯

	@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);
	}

結果:兩表資料都回滾了
在這裡插入圖片描述
在這裡插入圖片描述

1.3、父方法開啟事務,子方法開啟事務,子方法報錯

	@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("子方法報錯");
	}

結果:兩表資料都回滾了
在這裡插入圖片描述
在這裡插入圖片描述

總結

父方法無事務,子方法開啟新事務
父方法有事務,子方法和父方法共用一個事務(無論父、子方法報錯,整體回滾)

2、SUPPORTS

2.1、父方法無事務,子方法開啟事務,子方法報錯

	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("子方法報錯");
	}

結果:資料都插入成功
在這裡插入圖片描述
在這裡插入圖片描述

2.2、父方法開啟事務,子方法開啟事務,子方法報錯

	@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("子方法報錯");
	}

結果:兩表資料都回滾了
在這裡插入圖片描述
在這裡插入圖片描述

總結

如果當前不存在事務,就以非事務執行
如果當前存在事務,就加入該事務

3、MANDATORY

3.1、父方法無事務,子方法開啟事務,子方法報錯

	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’

3.2、父方法開啟事務,子方法開啟事務,子方法報錯

	@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("子方法報錯");
	}

結果:兩表資料都回滾了
在這裡插入圖片描述
在這裡插入圖片描述

總結

如果當前不存在事務,就丟擲異常
如果當前存在事務,就加入該事務

4、REQUIRES_NEW

4.1、父方法無事務,子方法開啟事務,子方法都報錯

	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資料回滾
在這裡插入圖片描述
在這裡插入圖片描述

4.2、父方法開啟事務,子方法開啟事務,父方法報錯

	@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資料插入成功
在這裡插入圖片描述
在這裡插入圖片描述

總結

無論當前存不存在事務,都建立新事務

5、NOT_SUPPORTED

5.1、父方法無事務,子方法開啟事務,子方法報錯

	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("子方法報錯");
	}

結果:資料都插入成功
在這裡插入圖片描述
在這裡插入圖片描述

5.2、父方法開啟事務,子方法開啟事務,子方法報錯

	@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資料插入成功
在這裡插入圖片描述
在這裡插入圖片描述

總結

以非事務方式執行,如果當前存在事務,父方法以事務方式執行,子方法以非事務方式執行

6、NEVER

父方法開啟事務,子方法開啟事務

	@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’

總結

以非事務方式執行,如果當前存在事務,則丟擲異常

7、NESTED

7.1、父方法無事務,子方法開啟事務,子方法報錯

	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資料回滾
在這裡插入圖片描述
在這裡插入圖片描述

7.2、父方法開啟事務,子方法開啟事務,子方法報錯

	@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("子方法報錯");
	}

結果:資料都回滾
在這裡插入圖片描述
在這裡插入圖片描述

7.3、父方法開啟事務,子方法開啟事務,父方法報錯

	@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);
	}

結果:資料都回滾
在這裡插入圖片描述
在這裡插入圖片描述

7.4、父方法開啟事務,子方法開啟事務,子方法報錯,父方法並捕獲

	@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資料回滾
在這裡插入圖片描述
在這裡插入圖片描述

總結

如果當前沒有事務,則新開事務執行
如果當前存在事務,則在巢狀事務內執行

四、結論

1、NESTED和REQUIRES區別

區別在於:如果當前存在事務,子方法拋異常時
NESTED在父方法可以選擇捕獲子方法,父方法資料不會回滾;
REQUIRES無論捕不捕獲,父方法資料都回滾

2、NESTED和REQUIRES_NEW區別

區別:如果當前存在事務,父方法拋異常時
NESTED資料回滾,REQUIRES也是如此
REQUIRES_NEW資料不回滾

3、七種傳播行為總結

說明:加入該事務,指的是父、子方法共用一個事務(無論父、子方法報錯,整體回滾)

REQUIRED

父方法無事務,子方法開啟新事務
父方法有事務,就加入該事務

SUPPORTS

如果當前不存在事務,就以非事務執行
如果當前存在事務,就加入該事務

MANDATORY

如果當前不存在事務,就丟擲異常
如果當前存在事務,就加入該事務

REQUIRES_NEW

無論當前存不存在事務,都建立新事務

NOT_SUPPORTED

以非事務方式執行,如果當前存在事務,父方法以事務方式執行,子方法以非事務方式執行

NEVER

以非事務方式執行,如果當前存在事務,則丟擲異常

NESTED

如果當前沒有事務,則新開事務執行
如果當前存在事務,則在巢狀事務內執行