1.事務的傳播級別
1)@Transactional(propagation=Propagation.REQUIRED):默認的spring事務傳播級別,使用該級別的特點是,如果上下文中已經存在事務,那么就加入到事務中執行,如果當前上下文中不存在事務,則新建事務執行,所以這個級別通常能滿足處理大多數的業務場景。
2)@Transactional(propagation=PROPAGATION.SUPPORTS):從字面意思就知道,supports(支持),該傳播級別的特點是,如果上下文存在事務,則支持當前事務,加入到事務執行,如果沒有事務,則使用非事務的方式執行。所以說,並非所有的包在transactionTemplate.execute中的代碼都會有事務支持。這個通常是用來處理那些並非原子性的非核心業務邏輯操作,應用場景較少。
3)@Transactional(propagation=PROPAGATION.MANDATORY):該級別的事務要求上下文中必須要存在事務,否則就會拋出異常!配置該方式的傳播級別是有效的控制上下文調用代碼遺漏添加事務控制的保證手段。比如一段代碼不能單獨被調用執行,但是一旦被調用,就必須有事務包含的情況,就可以使用這個傳播級別。
4)@Transactional(propagation=PROPAGATION.REQUIRES_NEW):從字面即可知道,每次都要一個新的事務,該傳播級別的特點是,每次都會新建一個事務,並且同時將上下文中的事務掛起,當新建事務執行完成以后,上下文事務再恢復執行。
這是一個很有用的傳播級別,舉一個應用場景:現在有一個發送100個紅包的操作,在發送之前,要做一些系統的初始化、驗證、數據記錄操作,然后發送100封紅包,然后再記錄發送日志,發送日志要求100%的准確,如果日志不准確,那么整個父事務邏輯需要回滾。
怎么處理整個業務需求呢?就是通過這個PROPAGATION.REQUIRES_NEW 級別的事務傳播控制就可以完成。發送紅包的子事務不會直接影響到父事務的提交和回滾。
5)@Transactional(propagation=PROPAGATION.NOT_SUPPORTED) :這個也可以從字面得知,not supported(不支持),當前級別的特點是,如果上下文中存在事務,
則掛起事務,執行當前邏輯,結束后恢復上下文的事務。
這個級別有什么好處?可以幫助你將事務極可能的縮小。我們知道一個事務越大,它存在的風險也就越多。所以在處理事務的過程中,要保證盡可能的縮小范圍。比如一段代碼,是每次邏輯操作都必須調用的,比如循環1000次的某個非核心業務邏輯操作。這樣的代碼如果包在事務中,勢必造成事務太大,導致出現一些難以考慮周全的異常情況。所以這個事務這個級別的傳播級別就派上用場了,用當前級別的事務模板抱起來就可以了。
6)@Transactional(propagation=PROPAGATION.NEVER):該事務更嚴格,上面一個事務傳播級別只是不支持而已,有事務就掛起,而PROPAGATION_NEVER傳播級別要求上下文中不能存在事務,一旦有事務,就拋出runtime異常,強制停止執行!
7)@Transactional(propagation=PROPAGATION.NESTED):字面也可知道,nested,嵌套級別事務。該傳播級別特征是,如果上下文中存在事務,則嵌套事務執行,如果不存在事務,則新建事務。
那么什么是嵌套事務呢?
嵌套是子事務套在父事務中執行,子事務是父事務的一部分,在進入子事務之前,父事務建立一個回滾點,叫save point,然后執行子事務,這個子事務的執行也算是父事務的一部分,然后子事務執行結束,父事務繼續執行。重點就在於那個save point,看幾個問題就明白了。
如果子事務回滾,會發生什么?
如果子事務拋出的異常被父事務捕獲,則父事務仍然可以提交;如果沒有捕獲,則父事務也將回滾。
如果父事務回滾,會發生什么?
父事務回滾,子事務也會跟着回滾!為什么呢,因為父事務結束之前,子事務是不會提交的,我們說子事務是父事務的一部分,正是這個道理。
那么:事務的提交,是什么情況? 是父事務先提交,然后子事務提交,還是子事務先提交,父事務再提交?
答案是第二種情況,還是那句話,子事務是父事務的一部分,由父事務統一提交。
以上是事務的7個傳播級別,在日常應用中,通常可以滿足各種業務需求,但是除了傳播級別,在讀取數據庫的過程中,如果兩個事務並發執行,那么彼此之間的數據是如何影響的呢?
這就需要了解一下事務的另一個特性:事務的隔離級別。
2.事務的隔離級別
1)@Transactional(isolation = Isolation.SERIALIZABLE):最嚴格的級別,事務串行執行,資源消耗最大;
2)@Transactional(isolation = Isolation.REPEATABLE_READ):保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的數據。避免了“臟讀取”和“不可重復讀取”的情況,但是帶來了更多的性能損失。
3)@Transactional(isolation = Isolation.READ_COMMITTED):大多數主流數據庫的默認事務等級,保證了一個事務不會讀到另一個並行事務已修改但未提交的數據,避免了“臟讀取”,該級別適用於大多數系統。
4)@Transactional(isolation = Isolation.READ_UNCOMMITTED):保證了讀取過程中不會讀取到非法數據。
1:Dirty reads--讀臟數據。也就是說,比如事務A的未提交(還依然緩存)的數據被事務B讀走,如果事務A失敗回滾,會導致事務B所讀取的的數據是錯誤的。
2:non-repeatable reads--不可重復讀。比如事務A中兩處讀取同一行數據的total值。在第一讀的時候,total是100,然后事務B就把total的數據改成200,事務A再讀一次,結果就發現,total竟然就變成200了,造成事務A在同一個事務中重復讀取的結果不一致,也就是所謂的不可重復讀。
3:phantom reads--幻象讀數據。這個和non-repeatable reads相似,也是同一個事務中多次讀不一致的問題。但是non-repeatable reads的不一致是因為他所要取的數據被修改了,但是phantom reads所要讀的數據的不一致指的是:相同的查詢語句執行兩次,但第二次返回第一次沒有返回的行,則該行是“幻像”行,也就是出現了幻讀。比如SELECT * FROM child WHERE id > 100 FOR UPDATE;,此時表中有id=90,id=102兩行數據。事務A第一次讀取時返回了1行數據,之后事務B往該表插入了一條id=101的數據,事務第二次讀取的時候,結果返回了2行數據,則出現了幻讀。
不可重復讀的重點是修改:同樣的條件, 你讀取過的數據, 再次讀取出來發現值不一樣了。
幻讀的重點在於新增或者刪除:同樣的條件, 第1次和第2次讀出來的記錄數不一樣。
而事務的隔離級別會導致讀取到非法數據的情況如下表示:
常用數據庫默認事務隔離級別
MYSQL:默認為REPEATABLE_READ
SQLSERVER:默認為READ_COMMITTED
ORACLE:默認為READ_COMMITTED
3.@Transactional注解中常用參數說明
1.)readOnly
該屬性用於設置當前事務是否為只讀事務,設置為true表示只讀,false則表示可讀寫,默認值為false。例如:@Transactional(readOnly=true)
2.)rollbackFor
該屬性用於設置需要進行回滾的異常類數組,當方法中拋出指定異常數組中的異常時,則進行事務回滾。例如:
指定單一異常類:@Transactional(rollbackFor=RuntimeException.class)
指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
3.)rollbackForClassName
該屬性用於設置需要進行回滾的異常類名稱數組,當方法中拋出指定異常名稱數組中的異常時,則進行事務回滾。例如:
指定單一異常類名稱:@Transactional(rollbackForClassName="RuntimeException")
指定多個異常類名稱:@Transactional(rollbackForClassName={"RuntimeException","Exception"})
4.)noRollbackFor
該屬性用於設置不需要進行回滾的異常類數組,當方法中拋出指定異常數組中的異常時,不進行事務回滾。例如:
指定單一異常類:@Transactional(noRollbackFor=RuntimeException.class)
指定多個異常類:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})
5.)noRollbackForClassName
該屬性用於設置不需要進行回滾的異常類名稱數組,當方法中拋出指定異常名稱數組中的異常時,不進行事務回滾。例如:
指定單一異常類名稱:@Transactional(noRollbackForClassName="RuntimeException")
指定多個異常類名稱:
@Transactional(noRollbackForClassName={"RuntimeException","Exception"})
propagation
該屬性用於設置事務的傳播行為,具體取值可參考表6-7。
例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
isolation
該屬性用於設置底層數據庫的事務隔離級別,事務隔離級別用於處理多事務並發的情況,通常使用數據庫的默認隔離級別即可,基本不需要進行設置
6.)timeout
該屬性用於設置事務的超時秒數,默認值為-1表示永不超時
4.注意的幾點:
1)@Transactional只能被應用到public方法上,對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能.
2)用spring事務管理器,由spring來負責數據庫的打開,提交,回滾。默認遇到運行期例外(throw new RuntimeException("注釋");)會回滾,即遇到不受檢查(unchecked)的例外時回滾;而遇到需要捕獲的例外(throw new Exception("注釋");)不會回滾,即遇到受檢查的例外(就是非運行時拋出的異常,編譯器會檢查到的異常叫受檢查例外或說受檢查異常)時,需我們指定方式來讓事務回滾 要想所有異常都回滾,要加上 @Transactional( rollbackFor={Exception.class,其它異常})。如果讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class)
如下:
@Transactional(rollbackFor=Exception.class) //指定回滾,遇到異常Exception時回滾
public void methodName() {
throw new Exception("注釋");
}
@Transactional(noRollbackFor=Exception.class)//指定不回滾,遇到運行期例外(throw new RuntimeException("注釋");)會回滾
public ItimDaoImpl getItemDaoImpl() {
throw new RuntimeException("注釋");
}
3)@Transactional注解應該只被應用到public可見度的方法上。如果你在 protected、private或者package-visible的方法上使用@Transactional注解它也不會報錯, 但是這個被注解的方法將不會展示已配置的事務設置。
4)@Transactional注解可以被應用於接口定義和接口方法、類定義和類的public方法上。然而,請注意僅僅@Transactional注解的出現不足於開啟事務行為,它僅僅 是一種元數據,能夠被可以識別@Transactional注解和上述的配置適當的具有事務行為的beans所使用。上面的例子中,其實正是 <tx:annotation-driven/>元素的出現 開啟 了事務行為。
5)Spring團隊的建議是你在具體的類(或類的方法)上使用@Transactional注解,而不要使用在類所要實現的任何接口上。你當然可以在接口上使用 @Transactional 注解,但是這將只能當你設置了基於接口的代理時它才生效。因為注解是不能繼承的,這就意味着如果你正在使用基於類的代理時,那么事務的設置將不能被基於類的代理所識別,而且對象也將不會被事務代理所包裝(將被確認為嚴重的)。因此,請接受Spring團隊的建議並且在具體的類上使用 @Transactional 注解。