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 5 private ProductDao productDao; 6 7 @Autowired 8 private ProductServiceB productServiceB; 9 10 @Transactional(propagation = Propagation.REQUIRED) 11 public void A() { 12 productDao.insert(); 13 productServiceB.B(); 14 } 15 } 16 17 @Service 18 public class ProductServiceB { 19 20 @Autowired 21 private ProductDao productDao; 22 23 @Transactional(propagation = Propagation.REQUIRED) 24 public void B() { 25 productDao.update(); 26 throw new ArithmeticException("測試異常"); 27 } 28 }
測試結果:A和B都沒有執行成功
2.A方法開啟事務,A方法中拋出異常
1 @Service 2 public class ProductServiceA { 3 4 @Autowired 5 private ProductDao productDao; 6 7 @Autowired 8 private ProductServiceB productServiceB; 9 10 @Transactional(propagation = Propagation.REQUIRED) 11 public void A() { 12 productDao.insert(); 13 productServiceB.B(); 14 15 throw new ArithmeticException("測試異常"); 16 } 17 } 18 19 @Service 20 public class ProductServiceB { 21 22 @Autowired 23 private ProductDao productDao; 24 25 @Transactional(propagation = Propagation.REQUIRED) 26 public void B() { 27 productDao.update(); 28 } 29 }
測試結果:A和B都沒有執行成功
3.A方法沒開啟事務,A方法中拋出異常
1 @Service 2 public class ProductServiceA { 3 4 @Autowired 5 private ProductDao productDao; 6 7 @Autowired 8 private ProductServiceB productServiceB; 9 10 public void A() { 11 productDao.insert(); 12 productServiceB.B(); 13 throw new ArithmeticException("測試異常"); 14 } 15 } 16 @Service 17 public class ProductServiceB { 18 19 @Autowired 20 private ProductDao productDao; 21 22 @Transactional(propagation = Propagation.NEVER) 23 public void B() { 24 productDao.update(); 25 } 26 }
測試結果:A和B都執行成功
4.A方法沒開啟事務,B方法中拋出異常
1 @Service 2 public class ProductServiceA { 3 4 @Autowired 5 private ProductDao productDao; 6 @Autowired 7 private ProductServiceB productServiceB; 8 9 //@Transactional(propagation = Propagation.REQUIRED) 10 public void A() { 11 productDao.insert(); 12 productServiceB.B(); 13 } 14 } 15 @Service 16 public class ProductServiceB { 17 18 @Autowired 19 private ProductDao productDao; 20 21 @Transactional(propagation = Propagation.REQUIRED) 22 public void B() { 23 productDao.update(); 24 throw new 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 5 private ProductDao productDao; 6 @Autowired 7 private ProductServiceB productServiceB; 8 9 @Transactional(propagation = Propagation.REQUIRED) 10 public void A() { 11 productDao.insert(); 12 productServiceB.B(); 13 } 14 } 15 @Service 16 public class ProductServiceB { 17 18 @Autowired 19 private ProductDao productDao; 20 21 @Transactional(propagation = Propagation.REQUIRES_NEW) 22 public void B() { 23 productDao.update(); 24 throw new ArithmeticException("測試異常"); 25 } 26 }
測試結果:A和B都執行失敗
2.A方法沒開啟事務,A方法自定義四回滾異常,B方法中拋出異常
1 @Service 2 public class ProductServiceA { 3 4 @Autowired 5 private ProductDao productDao; 6 @Autowired 7 private ProductServiceB productServiceB; 8 9 //自定義回滾異常 10 @Transactional(propagation = Propagation.REQUIRED,noRollbackFor = ArithmeticException.class) 11 public void A() { 12 productDao.insert(); 13 productServiceB.B(); 14 } 15 } 16 @Service 17 public class ProductServiceB { 18 19 @Autowired 20 private ProductDao productDao; 21 22 @Transactional(propagation = Propagation.REQUIRES_NEW) 23 public void B() { 24 productDao.update(); 25 throw new ArithmeticException("測試異常"); 26 } 27 }
測試結果:A方法執行成功,B方法執行失敗
3.A方法開啟事務,A方法拋異常
1 @Service 2 public class ProductServiceA { 3 4 @Autowired 5 private ProductDao productDao; 6 @Autowired 7 private ProductServiceB productServiceB; 8 9 //自定義回滾異常 10 @Transactional(propagation = Propagation.REQUIRED) 11 public void A() { 12 productDao.insert(); 13 productServiceB.B(); 14 throw new ArithmeticException("測試異常"); 15 } 16 } 17 @Service 18 public class ProductServiceB { 19 20 @Autowired 21 private ProductDao productDao; 22 23 @Transactional(propagation = Propagation.REQUIRES_NEW) 24 public void B() { 25 productDao.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 5 private ProductDao productDao; 6 @Autowired 7 private ProductServiceB productServiceB; 8 9 //自定義回滾異常 10 @Transactional(propagation = Propagation.REQUIRED) 11 public void A() { 12 productDao.insert(); 13 productServiceB.B(); 14 throw new ArithmeticException("測試異常"); 15 } 16 } 17 @Service 18 public class ProductServiceB { 19 20 @Autowired 21 private ProductDao productDao; 22 23 @Transactional(propagation = Propagation.NESTED) 24 public void B() { 25 productDao.update(); 26 27 } 28 }
測試結果:A和B都執行失敗。
2.B方法拋出異常,主事務可選擇性處理該異常
1 @Service 2 public class ProductServiceA { 3 4 @Autowired 5 private ProductDao productDao; 6 @Autowired 7 private ProductServiceB productServiceB; 8 9 //自定義回滾異常 10 @Transactional(propagation = Propagation.REQUIRED,noRollbackFor = ArithmeticException.class) 11 //@Transactional(propagation = Propagation.REQUIRED) 12 public void A() { 13 productDao.insert(); 14 productServiceB.B(); 15 16 } 17 } 18 @Service 19 public class ProductServiceB { 20 21 @Autowired 22 private ProductDao productDao; 23 24 @Transactional(propagation = Propagation.NESTED) 25 public void B() { 26 productDao.update(); 27 throw new 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也不會處理這個傳播行為。
Spring事務傳播