幾個概念要清楚:事務的傳播機制,事務的邊界數據庫
工做原理數組
運行配置@Transactional註解的測試類的時候,具體會發生以下步驟網絡
1)事務開始時,經過AOP機制,生成一個代理connection對象,並將其放入DataSource實例的某個與DataSourceTransactionManager相關的某處容器中。在接下來的整個事務中,客戶代碼都應該使用該connection鏈接數據庫,執行全部數據庫命令[不使用該connection鏈接數據庫執行的數據庫命令,在本事務回滾的時候得不到回滾]app
2)事務結束時,回滾在第1步驟中獲得的代理connection對象上執行的數據庫命令,而後關閉該代理connection對象測試
@Transactional不生效的狀況spa
解決方法線程
簡單粗暴,有須要就在調用service中就開@Transactional,若是實在不行,像裏面有調用第三方資源,唔想事務開得過久,能夠考慮新建另一個service去開@Transactional。代理
那麼@Transactional如何工做?code
實現了EntityManager接口的持久化上下文代理並非聲明式事務管理的惟一部分,事實上包含三個組成部分:對象
1. EntityManager Proxy自己
2. 事務的切面
3. 事務管理器
事務的切面
事務的切面是一個「around(環繞)」切面,在註解的業務方法先後均可以被調用。實現切面的具體類是TransactionInterceptor。
事務的切面有兩個主要職責:
1. 在’before’時,切面提供一個調用點,來決定被調用業務方法應該在正在進行事務的範圍內運行,仍是開始一個新的獨立事務。
2. 在’after’時,切面須要肯定事務被提交,回滾或者繼續運行。
3. 在’before’時,事務切面自身不包含任何決策邏輯,是否開始新事務的決策委派給事務管理器完成。
事務管理器
事務管理器須要解決下面兩個問題:
新的Entity Manager是否應該被建立?
是否應該開始新的事務?
這些須要事務切面’before’邏輯被調用時決定。事務管理器的決策基於如下兩點:
1. 事務是否正在進行
2. 事務方法的propagation屬性(好比REQUIRES_NEW總要開始新事務)
若是事務管理器肯定要建立新事務,那麼將:
1. 建立一個新的entity manager
2. entity manager綁定到當前線程
3. 從數據庫鏈接池中獲取鏈接
4. 將鏈接綁定到當前線程
特色:
1. 使用ThreadLocal變量將entity manager和數據庫鏈接都綁定到當前線程。
2. 事務運行時他們存儲在線程中,當它們再也不被使用時,事務管理器決定是否將他們清除。
3. 程序的任何部分若是須要當前的entity manager和數據庫鏈接均可以從線程中獲取。
EntityManager proxy
EntityManager proxy(前面已經介紹過)就是謎題的最後一部分。當業務方法調用entityManager.persist()時,這不是由entity manager直接調用的。
而是業務方法調用代理,代理從線程獲取當前的entity manager,前面介紹過事務管理器將entity manager綁定到線程。
瞭解了@Transactional機制的各個部分,咱們來看一下實現它的經常使用Spring配置。
根據上面所述,咱們所使用的客戶代碼應該具備以下能力:
1)每次執行數據庫命令的時候
若是在事務的上下文環境中,那麼不直接建立新的connection對象,而是嘗試從DataSource實例的某個與DataSourceTransactionManager相關的某處容器中獲取connection對象;在非事務的上下文環境中,直接建立新的connection對象
2)每次執行完數據庫命令的時候
若是在事務的上下文環境中,那麼不直接關閉connection對象,由於在整個事務中都須要使用該connection對象,而只是釋放本次數據庫命令對該connection對象的持有;在非事務的上下文環境中,直接關閉該connection對象
在service類前加上@Transactional,聲明這個service全部方法須要事務管理。每個業務方法開始時都會打開一個事務。error是必定會回滾的
Spring默認狀況下會對運行期例外(RunTimeException)進行事務回滾。這個例外是unchecked
若是遇到checked意外就不回滾。
如何改變默認規則:
1 讓checked例外也回滾:在整個方法前加上 @Transactional(rollbackFor=Exception.class)
2 讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class)
3 不須要事務管理的(只查詢的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)
4 若是不添加rollbackFor等屬性,Spring碰到Unchecked Exceptions都會回滾,不只是RuntimeException,也包括Error。
注意:
若是異常被try{}catch{}了,事務就不回滾了,若是想讓事務回滾必須再往外拋try{}catch{throw Exception}。
1.檢查型異常(Checked Exception)
我的理解:所謂檢查(Checked)是指編譯器要檢查這類異常,檢查的目的一方面是由於該類異常的發生難以免,另外一方面就是讓開發者去解決掉這類異常,因此稱爲必須處理(try ...catch)的異常。若是不處理這類異常,集成開發環境中的編譯器通常會給出錯誤提示。
例如:一個讀取文件的方法代碼邏輯沒有錯誤,但程序運行時可能會由於文件找不到而拋出FileNotFoundException,若是不處理這些異常,程序未來確定會出錯。因此編譯器會提示你要去捕獲並處理這種可能發生的異常,不處理就不能經過編譯。
2.非檢查型異常(Unchecked Exception)
我的理解:所謂非檢查(Unchecked)是指編譯器不會檢查這類異常,不檢查的則開發者在代碼的編輯編譯階段就不是必須處理,這類異常通常能夠避免,所以無需處理(try ...catch)。若是不處理這類異常,集成開發環境中的編譯器也不會給出錯誤提示。
例如:你的程序邏輯自己有問題,好比數組越界、訪問null對象,這種錯誤你本身是能夠避免的。編譯器不會強制你檢查這種異常。
使用事務註解@Transactional 以前,應該先了解它的相關屬性,避免在實際項目中踩中各類各樣的坑點。
例以下面這段代碼,帳戶餘額依舊增長成功,並無由於後面遇到SQLException(檢測異常)而進行事務回滾!!
@Transactional public void addMoney() throws Exception { //先增長餘額 accountMapper.addMoney(); //而後遇到故障 throw new SQLException("發生異常了.."); }
緣由分析:由於Spring的默認的事務規則是遇到運行異常(RuntimeException及其子類)和程序錯誤(Error)纔會進行事務回滾,顯然SQLException並不屬於這個範圍。若是想針對檢測異常進行事務回滾,能夠在@Transactional 註解裏使用
rollbackFor 屬性明確指定異常。例以下面這樣,就能夠正常回滾:
@Transactional(rollbackFor = Exception.class) public void addMoney() throws Exception { //先增長餘額 accountMapper.addMoney(); //而後遇到故障 throw new SQLException("發生異常了.."); }
這是許多新手都會犯的一個錯誤,在業務層手工捕捉並處理了異常,你都把異常「吃」掉了,Spring天然不知道這裏有錯,更不會主動去回滾數據。例如:下面這段代碼直接致使增長餘額的事務回滾沒有生效。
@Transactional public void addMoney() throws Exception { //先增長餘額 accountMapper.addMoney(); //謹慎:儘可能不要在業務層捕捉異常並處理 try { throw new SQLException("發生異常了.."); } catch (Exception e) { e.printStackTrace(); } }
不要小瞧了這些細節,往前暴露異常很大程度上很可以幫咱們快速定位問題,而不是常常在項目上線後出現問題,卻沒法刨根知道哪裏報錯。
推薦作法:若非實際業務要求,則在業務層統一拋出異常,而後在控制層統一處理。
@Transactional public void addMoney() throws Exception { //先增長餘額 accountMapper.addMoney(); //推薦:在業務層將異常拋出 throw new RuntimeException("發生異常了.."); }