淺談Spring事務中的7種傳播特性

NO IMAGE

什麼是事務的傳播特性?

簡單來講,就是當系統中存在兩個事務方法時(我們暫稱為方法A和方法B),如果方法B在方法A中被調用,那麼將採用什麼樣的事務形式,就叫做事務的傳播特性

比如,A方法調用了B方法(B方法必須使用事務註解),那麼B事務可以是一個在A中嵌套的事務,或者B事務不使用事務,又或是使用與A事務相同的事務,這些均可以通過指定事務傳播特性來實現

怎麼配置事務傳播特性?

首先使用org.springframework.transaction.annotation包下的@Transactional註解,在其中聲明propagation屬性即可(默認值為Propagation.REQUIRED):

    @Transactional(propagation = Propagation.REQUIRED)

事務傳播特性有哪幾種?

目前,Spring在TransactionDefinition類中定義了以下7種傳播特性,具體特性我們接下來會分析:

  • PROPAGATION_REQUIRED:如果不存在外層事務,就主動創建事務;否則使用外層事務
  • PROPAGATION_SUPPORTS:如果不存在外層事務,就不開啟事務;否則使用外層事務
  • PROPAGATION_MANDATORY:如果不存在外層事務,就拋出異常;否則使用外層事務
  • PROPAGATION_REQUIRES_NEW:總是主動開啟事務;如果存在外層事務,就將外層事務掛起
  • PROPAGATION_NOT_SUPPORTED:總是不開啟事務;如果存在外層事務,就將外層事務掛起
  • PROPAGATION_NEVER:總是不開啟事務;如果存在外層事務,則拋出異常
  • PROPAGATION_NESTED:如果不存在外層事務,就主動創建事務;否則創建嵌套的子事務

為什麼要指定事務傳播特性?

有些人可能會覺得,為什麼非要指定傳播特性不可,我們所有方法執行都開啟一個事務不可以嗎?這裡我暫時不做解釋,看完下一個部分相信就有答案了

事務傳播特性各自有什麼特點?

在具體說明前,先來做一些準備工作

首先定義兩張表usernote,user表有idname兩個數據列,note表有idcontent兩個數據列

然後新建springboot項目,創建對應的User/Note類,以及dao和service接口等等部分(為了方便演示,Service我直接創建的類,沒有使用接口),就不再一一列出了

接著重點來了,我們先在UserService中定義insertUser方法:

    @Transactional(rollbackFor = Exception.class)
public void insertUser(String name) {
User user = new User(name);
userRepository.save(user);
// 插入用戶之後,我們插入一條用戶筆記
noteService.insertNote(name + "'s note");
}

對應的NoteService中的insertNote方法如下:

    @Transactional(rollbackFor = Exception.class)
public void insertUser(String name) {
User user = new User(name);
userRepository.save(user);
noteService.insertNote(name + "'s note");
}

現在我們再定義一個測試方法,注意要把自動回滾關閉:

    @Test
@Rollback(value = false)
public void test() {
userService.insertUser("hikari");
}

看一下現在的執行結果:

淺談Spring事務中的7種傳播特性
淺談Spring事務中的7種傳播特性

REQUIRED

如果不存在外層事務,就主動開啟事務;否則使用外層事務

雖然該類型是默認的傳播特性,不過我們還是手動指定一下,要記住的是,傳播特性是作用於內層方法上的,所以我們加在外層方法上是無效的:

    @Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
public void insertNote(String content) {
Note note = new Note(content);
noteRepository.save(note);
}

但是目前這兩個方法沒有任何干擾,所以我們手動製造點異常:

外層方法關閉事務,內層方法拋出異常

    // @Transactional(rollbackFor = Exception.class)    // ←
public void insertUser(String name) {
User user = new User(name);
userRepository.save(user);
noteService.insertNote(name + "'s note");
}
@Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
public void insertNote(String content) {
Note note = new Note(content);
noteRepository.save(note);
throw new RuntimeException();   // ←
}

按照REQUIRED傳播特性,如果外層方法沒有使用事務,則內層方法會主動開啟事務,運行結果如下:

淺談Spring事務中的7種傳播特性
淺談Spring事務中的7種傳播特性

我們可以發現外層方法沒有使用事務(user表中有數據),而內層方法使用了事務(note表進行了回滾,所以無數據)

外層方法拋出異常

    @Transactional(rollbackFor = Exception.class)
public void insertUser(String name) {
User user = new User(name);
userRepository.save(user);
noteService.insertNote(name + "'s note");
throw new RuntimeException();   // ←
}
@Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
public void insertNote(String content) {
Note note = new Note(content);
noteRepository.save(note);
}

運行結果:

淺談Spring事務中的7種傳播特性
淺談Spring事務中的7種傳播特性

兩個表中均無數據,說明外層方法和內層方法使用的是一個事務,所以發生異常一起回滾

SUPPORTS

如果不存在外層事務,就不開啟事務;否則使用外層事務

為了避免閱讀疲勞,存在外層事務則使用同一個事務這個特性就不演示了,我們演示前一個特性:

外層方法關閉事務,內層方法拋出異常

//  @Transactional(rollbackFor = Exception.class)   // ←
public void insertUser(String name) {
User user = new User(name);
userRepository.save(user);
noteService.insertNote(name + "'s note");
}
@Transactional(rollbackFor = Exception.class,  propagation = Propagation.SUPPORTS)
public void insertNote(String content) {
Note note = new Note(content);
noteRepository.save(note);
throw new RuntimeException();   // ←
}

結果如下:

淺談Spring事務中的7種傳播特性

其中note表有數據,說明內層方法沒有啟用事務

MANDATORY

如果不存在外層事務,就拋出異常;否則使用外層事務

外層方法關閉事務

//  @Transactional(rollbackFor = Exception.class)   // ←
public void insertUser(String name) {
User user = new User(name);
userRepository.save(user);
noteService.insertNote(name + "'s note");
}
@Transactional(rollbackFor = Exception.class,  propagation = Propagation.MANDATORY)
public void insertNote(String content) {
Note note = new Note(content);
noteRepository.save(note);
}

運行結果:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

說明必須要有外部事務的存在才能執行

與上面類似,“如果外層方法事務存在,則內層方法使用同一個事務” 這個特性在這裡也不贅述了

REQUIRES_NEW

總是主動開啟事務;如果存在外層事務,就將外層事務掛起

外層方法拋出異常

    @Transactional(rollbackFor = Exception.class)
public void insertUser(String name) {
User user = new User(name);
userRepository.save(user);
noteService.insertNote(name + "'s note");
throw new RuntimeException();   // ←
}
@Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRES_NEW)
public void insertNote(String content) {
Note note = new Note(content);
noteRepository.save(note);
}

運行結果:

淺談Spring事務中的7種傳播特性
淺談Spring事務中的7種傳播特性

我們可以看到,外層方法的事務被回滾,而內層方法的事務並沒有跟著一起回滾,所以它們使用的不是同一個事務,兩個事務不會相互影響

但是有些人可能會覺得,如果內層方法拋出異常,外層方法的事務應該也不會回滾吧?很遺憾並不是這樣的,不要被事務迷惑了,內層方法拋出異常(未被try-catch捕獲),相當於外層方法拋出異常,所以外層方法的事務依然會回滾

NOT_SUPPORTED

總是不開啟事務;如果存在外層事務,就將外層事務掛起

內層方法拋出異常

    @Transactional(rollbackFor = Exception.class)
public void insertUser(String name) {
User user = new User(name);
userRepository.save(user);
noteService.insertNote(name + "'s note");
}
@Transactional(rollbackFor = Exception.class,  propagation = Propagation.NOT_SUPPORTED)
public void insertNote(String content) {
Note note = new Note(content);
noteRepository.save(note);
throw new RuntimeException();   // ←
}

運行結果:

淺談Spring事務中的7種傳播特性
淺談Spring事務中的7種傳播特性

user表中無數據,說明外層方法使用了事務,發生了回滾;而note表中有數據,說明沒有回滾,沒有啟用事務

NEVER

總是不開啟事務;如果存在外層事務,則拋出異常

外層方法開啟事務

    @Transactional(rollbackFor = Exception.class)
public void insertUser(String name) {
User user = new User(name);
userRepository.save(user);
noteService.insertNote(name + "'s note");
}
@Transactional(rollbackFor = Exception.class,  propagation = Propagation.NEVER)
public void insertNote(String content) {
Note note = new Note(content);
noteRepository.save(note);
}

運行結果:

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

淺談Spring事務中的7種傳播特性
淺談Spring事務中的7種傳播特性

MANDATORY的特性正好相反,MANDATORY是當外層方法不存在事務拋出異常,而NEVER是當外層方法存在事務拋出異常

NESTED

如果不存在外層事務,就主動創建事務;否則創建嵌套的子事務

外層方法拋出異常

    @Transactional(rollbackFor = Exception.class)
public void insertUser(String name) {
User user = new User(name);
userRepository.save(user);
noteService.insertNote(name + "'s note");
throw new RuntimeException();   // ←
}
@Transactional(rollbackFor = Exception.class,  propagation = Propagation.NESTED)
public void insertNote(String content) {
Note note = new Note(content);
noteRepository.save(note);
}

運行結果:

org.springframework.transaction.NestedTransactionNotSupportedException: JpaDialect does not support savepoints - check your JPA provider's capabilities

有些人可能會遇到這樣的錯誤,是因為jpa/hibernate是不支持“savepoint”特性的,而NESTED依賴於 “savepoint” 機制,可以在內層方法執行前創建一個“暫存點”,然後開啟嵌套的子事務,如果子事務發生異常,會直接回滾到這個暫存點,而不會導致整體事務的回滾

不過沒關係,我們可以使用jdbcTemplate,修改後的方法如下:

    @Transactional(rollbackFor = Exception.class)
public void insertUser(String name) {
User user = new User(name);
userDao.insertUser(user);
noteService.insertNote(name + "'s note");
throw new RuntimeException();
}
@Transactional(rollbackFor = Exception.class,  propagation = Propagation.NESTED)
public void insertNote(String content) {
Note note = new Note(content);
noteDao.insertNote(note);
}

這裡就不做測試了(其實是因為代碼還是有bug,等調好了我會把執行結果放上來),執行結果是兩表中均無數據,因為嵌套的子事務依然屬於外層的事務,所以外層事務的回滾會連帶著嵌套的子事務一起回滾

NESTED/REQUIRES_NEWREQUIRED有什麼區別?NESTEDREQUIRES_NEW有什麼區別?

NESTED/REQUIRES_NEWREQUIRED的區別

先來談一下它們之間的相同點,如果外層方法不存在事務時,這兩種方式均會創建一個新事務,這一點是一致的

不同點在於,如果外層方法存在事務時,REQUIRED會和外層方法使用同一個事務,而NESTED會創建一個嵌套的子事務,這兩種方式最重要的區別就在這裡:如果內層方法拋出異常,當使用REQUIRED方式時,即使在外層方法捕獲了該異常,也依然會導致外層事務回滾(因為使用的是同一個事務);而如果使用NESTEDREQUIRES_NEW的方式,只要在外層方法捕獲了該異常,就不會導致外層事務回滾

    @Transactional(rollbackFor = Exception.class)
public void insertUser(String name) {
User user = new User(name);
userRepository.save(user);
try {
noteService.insertNote(name + "'s note");
} catch(Exception e) {
e.printStackTrace();
}
}
@Transactional(rollbackFor = Exception.class,  propagation = Propagation.REQUIRED)
public void insertNote(String content) {
Note note = new Note(content);
noteRepository.save(note);
throw new RuntimeException();
}

運行結果:

淺談Spring事務中的7種傳播特性
淺談Spring事務中的7種傳播特性

兩張表均無數據,即使外層方法捕獲了內層方法的異常,還是會導致整體回滾,因為使用的是同一個事務

NESTEDREQUIRES_NEW的區別

這兩種方式都相當於開了一個新事務,但是它們之間最重要的區別就是,NESTED是一個嵌套的子事務,如果外層事務回滾,則這個子事務會被一起回滾,而REQUIRES_NEW的方法則不會

使用場景

不重要的項目/普通場景/懶得考慮那麼多

什麼都不填 / REQUIRED

內層方法與外層方法幾乎沒有關聯,相當於獨立執行

REQUIRES_NEW

內層方法依賴於外層方法,但是外層方法不希望被內層方法影響

在插入用戶信息後向日誌表中插入一條記錄

NESTED

內層方法需要和外層方法的操作同步,發生異常時要麼都不回滾,要麼一起回滾

SUPPORTS

內層方法不啟用事務,但是可以允許外層方法啟用事務

NOT_SUPPORTED

內層方法不啟用事務,也不允許外層方法啟用事務

NEVER

內層方法必須開啟事務,同時在邏輯上依賴於外層方法

MANDATORY

相關文章

手寫源碼(三):自己實現SpringMVC

手寫源碼(二):自己實現SpringIOC

手寫源碼(一):自己實現Spring事務

淺談Kafka特性與架構