據說微信搜索《Java魚仔》會變動強!java
本文收錄於JavaStarter ,裏面有我完整的Java系列文章,學習或面試均可以看看哦git
(一)前言
前段時間在面試的時候有個面試官問了這樣一個問題,@Transactional註解能在私有方法上使用嗎?若是如今這個問題問到你,你的答案是什麼?github
答案是在私有方法上添加@Transactional註解不能生效,日常在項目中沒有用到就不會有這個印象,所以寫了這篇文章來深刻了解一下Transactional的用法。面試
(二)幾個重要的參數
Transactional的做用是讓被修飾的方法以事務的方式運行,所謂事務就是指這個方法中的全部持久化操做要麼都進行,要麼都不進行。好比訂單系統中生成訂單和庫存刪減須要是事務操做。數據庫
Transactional的使用很簡單,在須要添加事務的方法處添加註解便可:微信
@Transactional(rollbackFor = Exception.class) public int createOrder(String orderId) { //增長訂單 orderMapper.createOrder(orderId); //扣除帳戶金額 accountMapper.updateAccount(10); return 1; }
接下來介紹Transactional的重要的參數:app
2.1 隔離級別 isolation
數據庫中可能會遇到的三種問題:分佈式
髒讀:一個事務讀到另外一個事務未提交的更新數據ide
不可重複讀 : 先後屢次讀取,數據內容不一致學習
幻讀 : 先後屢次讀取,數據總量不一致
Spring的隔離級別:
-
ISOLATION_DEFAULT: 這是一個 PlatfromTransactionManager 默認的隔離級別,使用數據庫默認的事務隔離級別.
-
ISOLATION_READ_UNCOMMITTED:讀未提交。這是事務最低的隔離級別,這種隔離級別會產生髒讀,不可重複讀和幻像讀。
-
ISOLATION_READ_COMMITTED:讀已提交,ORACLE默認隔離級別,有幻讀以及不可重複讀風險。
-
ISOLATION_REPEATABLE_READ: 可重複讀,解決不可重複讀的隔離級別,但仍是有幻讀風險。
-
ISOLATION_SERIALIZABLE 串行化,最高的事務隔離級別,無論多少事務,挨個運行完一個事務的全部子事務以後才能夠執行另一個事務裏面的全部子事務,解決髒讀、不可重複讀和幻讀。
隔離級別並不是越高越好,越高的隔離級別意味着越大的資源消耗,所以須要作適當取捨。
2.2 傳播屬性 propagation
事務的傳播屬性只在Spring中存在,在數據庫中的事務中不存在傳播屬性的說法
Spring的傳播屬性有如下七種:
PROPAGATION_REQUIRED--支持當前事務,若是當前沒有事務,就新建一個事務。(默認)
PROPAGATION_SUPPORTS--支持當前事務,若是當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY--支持當前事務,若是當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW--新建事務,若是當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED--以非事務方式執行操做,若是當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER--以非事務方式執行,若是當前存在事務,則拋出異常。
PROPAGATION_NESTED--若是當前存在事務,則在嵌套事務內執行。若是當前沒有事務,則進行與PROPAGATION_REQUIRED相似的操做。
2.2.1 重要傳播屬性的解釋:
假設有ServiceA.MethodA(),在其內部調用ServiceB.MethodB()
PROPAGATION_REQUIRED Spring默認使用PROPAGATION_REQUIRED傳播屬性,當ServiceA.MethodA()運行時,開啓一個事務,此時ServiceB.MethodB()方法發現已經存在一個事務,就不會再開啓事務,所以無論哪個方法報錯,都會回調。
PROPAGATION_REQUIRES_NEW 新建事務,若是當前存在事務,把當前事務掛起。當ServiceA.MethodA()運行時,開啓一個事務A。當運行ServiceB.MethodB()時,把事務A掛起,而後開啓事務B。就算事務A發生回滾,事務B依然能正常提交。總結起來就是:外部事務不會影響內部事務的提交和回滾。
PROPAGATION_NESTED 若是當前存在事務,則在嵌套事務內執行。關於嵌套須要首先了解檢查點的概念:當在事務中設置檢查點後,若是發生回滾,會回滾到設置檢查點的位置,而不是回滾整個事務。嵌套事務就使用了檢查點Savepoint。當ServiceA.MethodA()運行時,開啓一個事務A。當運行ServiceB.MethodB()時,開啓一個子事務B,它將取得一個 savepoint. 若是這個事務B失敗, 將回滾到此 savepoint,而不會影響整個事務。總結一句話就是內部事務不會影響外部事務的提交和回滾。
2.3 readOnly
默認狀況下是false,若是設置爲true表示該方法是可讀方法,若是存在數據的修改會拋出異常。
2.4 rollbackForClassName/rollbackFor
用來指明回滾的條件是哪些異常類或者異常類名。
2.5 norollbackForClassName/noRollbackFor
用來指明不回滾的條件是哪些異常類或者異常類名。
2.6 timeout
用於設置事務處理的時間長度,防止長事件的阻塞佔用系統資源。
2.7 value
指定要使用的Spring事務管理器。
(三)什麼狀況下Transactional會失效
3.1 修飾非public方法時
這就是開頭的問題了,當用Transactional修飾非public方法時,Transactional註解是不會生效的:
3.2 在類的內部調用事務方法
若是在一個類的內部調用了事務方法,Transactional也不會生效。
@Service public class OrderServiceImpl implements OrderService { @Override @Transactional(rollbackFor = Exception.class) public int createOrder(String orderId) { //增長訂單 orderMapper.createOrder(orderId); //扣除帳戶金額 accountMapper.updateAccount(10); return 1; } public void innerTransfer(){ createOrder("111"); } }
3.3 捕獲異常後未拋出
默認狀況下只會在捕獲了RuntimeException後Transactional註解纔會生效,若是在代碼中捕獲了異常後未拋出,則Transactional失效:
@Transactional(rollbackFor = Exception.class) public int createOrder(String orderId) { //增長訂單 orderMapper.createOrder(orderId); //扣除帳戶金額 accountMapper.updateAccount(10); try { int i=1/0; }catch (RuntimeException e){ e.printStackTrace(); } return 1; }
解決辦法之一是在catch後再拋出一次異常:
@Transactional(rollbackFor = Exception.class) public int createOrder(String orderId) { //增長訂單 orderMapper.createOrder(orderId); //扣除帳戶金額 accountMapper.updateAccount(10); try { int i=1/0; }catch (RuntimeException e){ e.printStackTrace(); throw new RuntimeException(); } return 1; }
(四)總結
Transactional註解須要重點關注兩個參數:isolation、propagation,以及三種失效狀況。另外若是存在數據源的切換,單體的Transactional將失效,這個時候就須要用到分佈式事務,好比seata。