Spring事務傳播
事務特性
事務有四大特性,分別如下:
1. 原子性(Atomicity):事務是資料庫邏輯工作單元,事務中包含的操作要麼都執行成功,要麼都執行失敗。
2. 一致性(Consistency):事務執行的結果必須是使資料庫資料從一個一致性狀態變到另外一種一致性狀態。當事務執行成功後就說資料庫處於一致性狀態。如果在執行過程中發生錯誤,這些未完成事務對資料庫所做的修改有一部分已寫入物理資料庫,這是資料庫就處於不一致狀態。
3. 隔離性(Isolation):一個事務的執行過程中不能影響到其他事務的執行,即一個事務內部的操作及使用的資料對其他事務是隔離的,併發執行各個事務之間無不干擾。
4. 持續性(Durability):即一個事務執一旦提交,它對資料庫資料的改變是永久性的。之後的其它操作不應該對其執行結果有任何影響。
事務導致的問題
1. 髒讀:指當一個事務正字訪問資料,並且對資料進行了修改,而這種資料還沒有提交到資料庫中,這時,另外一個事務也訪問這個資料,然後使用了這個資料。因為這個資料還沒有提交那麼另外一個事務讀取到的這個資料我們稱之為髒資料
2. 不可重複讀:指在一個事務內,多次讀同一資料。在這個事務還沒有執行結束,另外一個事務也訪問該同一資料,那麼在第一個事務中的兩次讀取資料之間,由於第二個事務的修改第一個事務兩次讀到的資料可能是不一樣的,這樣就發生了在一個事物內兩次連續讀到的資料是不一樣的,這種情況被稱為是不可重複讀。(針對行內資料欄位不一致)
3. 幻象:一個事務先後讀取一個範圍的記錄,但兩次讀取的紀錄數不同,我們稱之為幻象讀(兩次執行同一條 select 語句會出現不同的結果,第二次讀會增加一資料行)(針對查詢結果集條數不一致)
不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住滿足條件的行,解決幻讀需要鎖表
Spring中事務傳播屬性
Spring中事務很很多功能都是Spring藉助底層資源的功能來完成的,但是事務的傳播行為是通過Spring框架自身實現的,Spring中定義了7個以PROPAGATION_開頭的常量表示它的傳播屬性,具體如下:
1. PROPAGATION_REQUIRED:如果存在一個事務,則支援當前事務。如果沒有事務則開啟
2. PROPAGATION_SUPPORTS:如果存在一個事務,支援當前事務。如果沒有事務,則非事務的執行
3. PROPAGATION_MANDATORY:如果已經存在一個事務,支援當前事務。如果沒有一個活動的事務,則丟擲異常
4. PROPAGATION_REQUIRES_NEW:總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起
5. PROPAGATION_NOT_SUPPORTED:總是非事務地執行,並掛起任何存在的事務
6. PROPAGATION_NEVER: 總是非事務地執行,如果存在一個活動事務,則丟擲異常
7. PROPAGATION_NESTED:如果一個活動的事務存在,則執行在一個巢狀的事務中. 如果沒有活動事務, PROPAGATION_REQUIRED 屬性執行
Spring中事務的隔離級別
1. ISOLATION_DEFAULT:這是一個PlatfromTransactionManager預設的隔離級別,使用資料庫預設的事務隔離級別
2. ISOLATION_READ_UNCOMMITTED :這是事務最低的隔離級別,它充許別外一個事務可以看到這個事務未提交的資料。這種隔離級別會產生髒讀,不可重複讀和幻像讀。
3. ISOLATION_READ_COMMITTED :保證一個事務修改的資料提交後才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的資料。這種事務隔離級別可以避免髒讀出現,但是可能會出現不可重複讀和幻像讀。:
4. ISOLATION_REPEATABLE_READ :這種事務隔離級別可以防止髒讀,不可重複讀。但是可能出現幻像讀。
5. ISOLATION_SERIALIZABLE :這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。除了防止髒讀,不可重複讀外,還避免了幻讀。、
事務的隔離級別不同,有可能產生不同的錯誤現象,引用網上常用的一張圖說明隔離級別與各種問題的關係:
Spring中的事務超時
事務超時指的是一個事務所允許執行的最長時間,超過該時間限制後事務還沒有完成,將自動回滾事務。Spring框架中在 TransactionDefinition介面中以 int 的值來表示超時時間,單位是秒。預設設定為底層事務系統的超時值,如果底層資料庫事務系統沒有設定超時值,那麼就是none,沒有超時限制
Spring中事務的回滾規則
Spring事務管理器回滾一個事務的時機是在當前事務的上下文中丟擲異常時。Spring事務管理器會捕捉到未處理的異常,然後根據規則決定是否需要回滾當前事務。
預設配置下,spring只有在丟擲RuntimeException 異常(Errors也會導致事務回滾)時才回滾,而丟擲checked異常則不會導致事務回滾。但是我們也可以明確的配置在丟擲哪些異常時回滾事務,包括checked異常(或者配置為不回滾)。
Spring中事務傳播行為
下面通過在Springboot中的幾個示例,看一下Spring中不同的事務傳播行為。
PROPAGATION_REQUIRED
1.A 方法開啟事務,B 方法中丟擲異常
1 @Service 2 public class ProductServiceA { 3 4@Autowired 5private ProductDao productDao; 6 7 @Autowired 8 private ProductServiceB productServiceB; 9 10@Transactional(propagation = Propagation.REQUIRED) 11public void A() { 12productDao.insert(); 13productServiceB.B(); 14} 15 } 16 17 @Service 18 public class ProductServiceB { 19 20@Autowired 21private ProductDao productDao; 22 23@Transactional(propagation = Propagation.REQUIRED) 24public void B() { 25productDao.update(); 26thrownew ArithmeticException("測試異常"); 27} 28 }
測試結果:A和B都沒有執行成功
2.A 方法開啟事務,A 方法中丟擲異常
1 @Service 2 public class ProductServiceA { 3 4@Autowired 5private ProductDao productDao; 6 7 @Autowired 8 private ProductServiceB productServiceB; 9 10@Transactional(propagation = Propagation.REQUIRED) 11public void A() { 12productDao.insert(); 13productServiceB.B(); 14 15thrownew ArithmeticException("測試異常"); 16} 17 } 18 19 @Service 20 public class ProductServiceB { 21 22@Autowired 23private ProductDao productDao; 24 25@Transactional(propagation = Propagation.REQUIRED) 26public void B() { 27productDao.update(); 28} 29 }
測試結果:A和B都沒有執行成功
3.A 方法沒開啟事務,A 方法中丟擲異常
1 @Service 2 public class ProductServiceA { 3 4@Autowired 5private ProductDao productDao; 6 7 @Autowired 8 private ProductServiceB productServiceB; 9 10public void A() { 11productDao.insert(); 12productServiceB.B(); 13thrownew ArithmeticException("測試異常"); 14} 15 } 16 @Service 17 public class ProductServiceB { 18 19@Autowired 20private ProductDao productDao; 21 22@Transactional(propagation = Propagation.NEVER) 23public void B() { 24productDao.update(); 25} 26 }
測試結果:A和B都執行成功
4.A 方法沒開啟事務,B 方法中丟擲異常
1 @Service 2 public class ProductServiceA { 3 4@Autowired 5private ProductDao productDao; 6@Autowired 7private ProductServiceB productServiceB; 8 9//@Transactional(propagation = Propagation.REQUIRED) 10public void A() { 11productDao.insert(); 12productServiceB.B(); 13} 14 } 15 @Service 16 public class ProductServiceB { 17 18@Autowired 19private ProductDao productDao; 20 21@Transactional(propagation = Propagation.REQUIRED) 22public void B() { 23productDao.update(); 24thrownew ArithmeticException("測試異常"); 25} 26 }
測試結果:A執行成功,B執行失敗
綜上,PROPAGATION_REQUIRED屬性:如果當執行到B方法時,會判斷上下文中是否存在事務,如果存在,則加入,此時方法A和方法B用的是同一個事務,方法A和方法B共存亡。如果不存在則方法B會為自己分配一個事務,該事務與方法A無關,所以這種情況可能出現A成功,B執行失敗回滾了。PROPAGATION_REQUIRED屬性是Spring框架事務傳播的預設屬性。
RROPAGATION_REQUIRES_NEW
1.A 方法沒開啟事務,B 方法中丟擲異常
1 @Service 2 public class ProductServiceA { 3 4@Autowired 5private ProductDao productDao; 6@Autowired 7private ProductServiceB productServiceB; 8 9@Transactional(propagation = Propagation.REQUIRED) 10public void A() { 11productDao.insert(); 12productServiceB.B(); 13} 14 } 15 @Service 16 public class ProductServiceB { 17 18@Autowired 19private ProductDao productDao; 20 21@Transactional(propagation = Propagation.REQUIRES_NEW) 22public void B() { 23productDao.update(); 24thrownew ArithmeticException("測試異常"); 25} 26 }
測試結果:A和B都執行失敗
2.A 方法沒開啟事務,A 方法自定義四回滾異常,B 方法中丟擲異常
1 @Service 2 public class ProductServiceA { 3 4@Autowired 5private ProductDao productDao; 6@Autowired 7private ProductServiceB productServiceB; 8 9//自定義回滾異常 10@Transactional(propagation = Propagation.REQUIRED,noRollbackFor = ArithmeticException.class) 11public void A() { 12productDao.insert(); 13productServiceB.B(); 14} 15 } 16 @Service 17 public class ProductServiceB { 18 19@Autowired 20private ProductDao productDao; 21 22@Transactional(propagation = Propagation.REQUIRES_NEW) 23public void B() { 24productDao.update(); 25thrownew ArithmeticException("測試異常"); 26} 27 }
測試結果:A方法執行成功,B方法執行失敗
3.A 方法開啟事務,A 方法拋異常
1 @Service 2 public class ProductServiceA { 3 4@Autowired 5private ProductDao productDao; 6@Autowired 7private ProductServiceB productServiceB; 8 9//自定義回滾異常 10@Transactional(propagation = Propagation.REQUIRED) 11public void A() { 12productDao.insert(); 13productServiceB.B(); 14thrownew ArithmeticException("測試異常"); 15} 16 } 17 @Service 18 public class ProductServiceB { 19 20@Autowired 21private ProductDao productDao; 22 23@Transactional(propagation = Propagation.REQUIRES_NEW) 24public void B() { 25productDao.update(); 26 27} 28 }
測試結果:A方法執行失敗,B方法執行成功
4.A 方法開啟事務,A 方法拋異常
同PROPAGATION_REQUIRED中示例,B方法單獨開啟一個事務
5.A 方法開啟事務,B 方法拋異常
同PROPAGATION_REQUIRED中示例,B方法單獨開啟一個事務
綜上,RROPAGATION_REQUIRES_NEW屬性總是開啟一個新的事務,如果已經存在一個事務,則將這個事務掛起。PROPAGATION_REQUIRED 和RROPAGATION_REQUIRES_NEW的事務傳遞的區別在於後者總是新起一個事務,所以可能存在兩個獨立的事務。
PROPAGATION_SUPPORTS
當方法B加上@Transactional(propagation = Propagation.SUPPORTS)註解,執行到方法B時,會檢查上下文中是不是已經存在事務,存在則加入,此時相當於PROPAGATION_REQUIRED,不存在則以非事務方法執行,此時剛好和RROPAGATION_REQUIRES_NEW相反。這種方式總結起來就是有事務就加入,沒有就算。
PROPAGATION_NOT_SUPPORTED
當方法B加上@Transactional(propagation = Propagation.NOT_SUPPORTED)註解,執行方法B時,檢查上下文中有沒有事務,如果沒有則正常以非事務方式執行,如果有事務,則掛起當前事務,繼續以非事務方式執行完,然後在繼續執行當前事務。這種方式總結起來就是有事務也不支援。方法B一定以非事務方式執行,不存在回滾方法B的可能。
PROPAGATION_NESTED
1. A 方法丟擲異常
1 @Service 2 public class ProductServiceA { 3 4@Autowired 5private ProductDao productDao; 6@Autowired 7private ProductServiceB productServiceB; 8 9//自定義回滾異常 10@Transactional(propagation = Propagation.REQUIRED) 11public void A() { 12productDao.insert(); 13productServiceB.B(); 14thrownew ArithmeticException("測試異常"); 15} 16 } 17 @Service 18 public class ProductServiceB { 19 20@Autowired 21private ProductDao productDao; 22 23@Transactional(propagation = Propagation.NESTED) 24public void B() { 25productDao.update(); 26 27} 28 }
測試結果:A和B都執行失敗。
2.B 方法丟擲異常,主事務可選擇性處理該異常
1 @Service 2 public class ProductServiceA { 3 4@Autowired 5private ProductDao productDao; 6@Autowired 7private ProductServiceB productServiceB; 8 9//自定義回滾異常 10@Transactional(propagation = Propagation.REQUIRED,noRollbackFor = ArithmeticException.class) 11//@Transactional(propagation = Propagation.REQUIRED) 12public void A() { 13productDao.insert(); 14productServiceB.B(); 15 16} 17 } 18 @Service 19 public class ProductServiceB { 20 21@Autowired 22private ProductDao productDao; 23 24@Transactional(propagation = Propagation.NESTED) 25public void B() { 26productDao.update(); 27thrownew ArithmeticException("測試異常"); 28} 29 }
當方法B加上@Transactional(propagation = Propagation.NESTED)註解,執行方法B時會先判斷上下文中是不是存在事務,如果不存在則,相當於RROPAGATION_REQUIRES_NEW屬性,新起一個事務執行,如果存在,則在當前事務中巢狀一個事務,此時也出現兩個事務,是巢狀關係。如果主事務提交或者回滾,則巢狀事務也將提交或者回滾。巢狀事務異常,主事務可選擇回滾還是繼續執行,主要是看如何處理這個異常。
PROPAGATION_NESTED與RROPAGATION_REQUIRES_NEW的區別是PROPAGATION_NESTED建立的是外部事務的子事務, 如果外部事務commit/roolback, 巢狀事務也會被commit/rollback,而RROPAGATION_REQUIRES_NEW建立的是一個新的事務,與已存在的事務獨立,即方法A和方法B是分別獨立的兩個事務,方法B如果已經執行,此時方法A拋異常將不影響方法B。
PROPAGATION_NEVER
這種方式表明必須以非事務的方式執行,如果存在事務,則拋異常。
PROPAGATION_MANDATORY
這個和PROPAGATION_NEVER相反,必須以事務的方式執行,不存在則拋異常。
事務的傳播行為失效問題
如果在程式中出現在同一個類的內部出現呼叫另一個@Transactional註解函式,或者在private或者protected方法上使用@Transactional註解,則將可能出現傳播行為不起作用。
原因:由於Spring的事務是通過AOP來實現的,Spring中通過動態代理實現Aop功能,不管是jdk動態代理還是cglib動態代理,都不會處理目標類的private和protected方法。而如果在類的內部呼叫另外一個添加了@Transactional註解的函式,相當於通過this呼叫函式,而不是代理物件呼叫該函式,所以Spring也不會處理這個傳播行為。