簡單來說,就是當系統中存在兩個事務方法時(咱們暫稱爲方法A和方法B),若是方法B在方法A中被調用,那麼將採用什麼樣的事務形式,就叫作事務的傳播特性java
好比,A方法調用了B方法(B方法必須使用事務註解),那麼B事務能夠是一個在A中嵌套的事務,或者B事務不使用事務,又或是使用與A事務相同的事務,這些都可以經過指定事務傳播特性來實現spring
首先使用org.springframework.transaction.annotation
包下的@Transactional
註解,在其中聲明propagation屬性便可(默認值爲Propagation.REQUIRED):springboot
@Transactional(propagation = Propagation.REQUIRED)
複製代碼
目前,Spring在TransactionDefinition
類中定義瞭如下7種傳播特性,具體特性咱們接下來會分析:ide
有些人可能會以爲,爲何非要指定傳播特性不可,咱們全部方法執行都開啓一個事務不能夠嗎?這裏我暫時不作解釋,看完下一個部分相信就有答案了測試
在具體說明前,先來作一些準備工做spa
首先定義兩張表user
和note
,user表有id
和name
兩個數據列,note表有id
和content
兩個數據列hibernate
而後新建springboot項目,建立對應的User/Note類,以及dao和service接口等等部分(爲了方便演示,Service我直接建立的類,沒有使用接口),就再也不一一列出了3d
接着重點來了,咱們先在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
方法以下:code
@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");
}
複製代碼
看一下如今的執行結果:
若是不存在外層事務,就主動開啓事務;不然使用外層事務
雖然該類型是默認的傳播特性,不過咱們仍是手動指定一下,要記住的是,傳播特性是做用於內層方法上的,因此咱們加在外層方法上是無效的:
@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傳播特性,若是外層方法沒有使用事務,則內層方法會主動開啓事務,運行結果以下:
咱們能夠發現外層方法沒有使用事務(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);
}
複製代碼
運行結果:
兩個表中均無數據,說明外層方法和內層方法使用的是一個事務,因此發生異常一塊兒回滾
若是不存在外層事務,就不開啓事務;不然使用外層事務
爲了不閱讀疲勞,存在外層事務則使用同一個事務
這個特性就不演示了,咱們演示前一個特性:
外層方法關閉事務,內層方法拋出異常
// @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(); // ←
}
複製代碼
結果以下:
其中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.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'
複製代碼
說明必需要有外部事務的存在才能執行
與上面相似,「若是外層方法事務存在,則內層方法使用同一個事務」 這個特性在這裏也不贅述了
老是主動開啓事務;若是存在外層事務,就將外層事務掛起
外層方法拋出異常
@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);
}
複製代碼
運行結果:
咱們能夠看到,外層方法的事務被回滾,而內層方法的事務並無跟着一塊兒回滾,因此它們使用的不是同一個事務,兩個事務不會相互影響
可是有些人可能會以爲,若是內層方法拋出異常,外層方法的事務應該也不會回滾吧?很遺憾並非這樣的,不要被事務迷惑了,內層方法拋出異常(未被try-catch捕獲),至關於外層方法拋出異常,因此外層方法的事務依然會回滾
老是不開啓事務;若是存在外層事務,就將外層事務掛起
內層方法拋出異常
@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(); // ←
}
複製代碼
運行結果:
user表中無數據,說明外層方法使用了事務,發生了回滾;而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.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'
複製代碼
和MANDATORY
的特性正好相反,MANDATORY
是當外層方法不存在事務拋出異常,而NEVER
是當外層方法存在事務拋出異常
若是不存在外層事務,就主動建立事務;不然建立嵌套的子事務
外層方法拋出異常
@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_NEW
和REQUIRED
有什麼區別?NESTED
和REQUIRES_NEW
有什麼區別?NESTED/REQUIRES_NEW
和REQUIRED
的區別先來談一下它們之間的相同點,若是外層方法不存在事務時,這兩種方式均會建立一個新事務,這一點是一致的
不一樣點在於,若是外層方法存在事務時,REQUIRED
會和外層方法使用同一個事務,而NESTED
會建立一個嵌套的子事務,這兩種方式最重要的區別就在這裏:若是內層方法拋出異常,當使用REQUIRED
方式時,即便在外層方法捕獲了該異常,也依然會致使外層事務回滾(由於使用的是同一個事務);而若是使用NESTED
或REQUIRES_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();
}
複製代碼
運行結果:
兩張表均無數據,即便外層方法捕獲了內層方法的異常,仍是會致使總體回滾,由於使用的是同一個事務
NESTED
和REQUIRES_NEW
的區別這兩種方式都至關於開了一個新事務,可是它們之間最重要的區別就是,NESTED
是一個嵌套的子事務,若是外層事務回滾,則這個子事務會被一塊兒回滾,而REQUIRES_NEW
的方法則不會
不重要的項目/普通場景/懶得考慮那麼多
什麼都不填 / REQUIRED
內層方法與外層方法幾乎沒有關聯,至關於獨立執行
REQUIRES_NEW
內層方法依賴於外層方法,可是外層方法不但願被內層方法影響
在插入用戶信息後向日誌表中插入一條記錄
NESTED
內層方法須要和外層方法的操做同步,發生異常時要麼都不回滾,要麼一塊兒回滾
SUPPORTS
內層方法不啓用事務,可是能夠容許外層方法啓用事務
NOT_SUPPORTED
內層方法不啓用事務,也不容許外層方法啓用事務
NEVER
內層方法必須開啓事務,同時在邏輯上依賴於外層方法
MANDATORY