Spring @Transactional 事務機制

幾個概念要清楚:事務的傳播機制,事務的邊界數據庫

 

工做原理數組

運行配置@Transactional註解的測試類的時候,具體會發生以下步驟網絡

1)事務開始時,經過AOP機制,生成一個代理connection對象,並將其放入DataSource實例的某個與DataSourceTransactionManager相關的某處容器中。在接下來的整個事務中,客戶代碼都應該使用該connection鏈接數據庫,執行全部數據庫命令[不使用該connection鏈接數據庫執行的數據庫命令,在本事務回滾的時候得不到回滾]app

2)事務結束時,回滾在第1步驟中獲得的代理connection對象上執行的數據庫命令,而後關閉該代理connection對象測試

 

 

@Transactional不生效的狀況spa

  1. @Transactional  在private 方法是不生效
  2. 在同一個bean裏,嵌套的public方法@Transactional 也不生效

解決方法線程

      簡單粗暴,有須要就在調用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 以前,應該先了解它的相關屬性,避免在實際項目中踩中各類各樣的坑點。

 

常見坑點1:遇到檢測異常時,事務默認不回滾。

 

例以下面這段代碼,帳戶餘額依舊增長成功,並無由於後面遇到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("發生異常了..");
    }

常見坑點2: 在業務層捕捉異常後,發現事務不生效。

這是許多新手都會犯的一個錯誤,在業務層手工捕捉並處理了異常,你都把異常「吃」掉了,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("發生異常了..");
    }

 

  • Spring團隊的建議是你在具體的類(或類的方法)上使用 @Transactional 註解,而不要使用在類所要實現的任何接口上。你固然能夠在接口上使用 @Transactional 註解,可是這將只能當你設置了基於接口的代理時它才生效。由於註解是不能繼承的,這就意味着若是你正在使用基於類的代理時,那麼事務的設置將不能被基於類的代理所識別,並且對象也將不會被事務代理所包裝(將被確認爲嚴重的)。所以,請接受Spring團隊的建議而且在具體的類火方法上使用 @Transactional 註解。
  • @Transactional 註解標識的方法,處理過程儘可能的簡單。尤爲是帶鎖的事務方法,能不放在事務裏面的最好不要放在事務裏面。能夠將常規的數據庫查詢操做放在事務前面進行,而事務內進行增、刪、改、加鎖查詢等操做
  • @Transactional 註解的默認事務管理器bean是「transactionManager」,若是聲明爲其餘名稱的事務管理器,須要在方法上添加@Transational(「managerName」)。
  • @Transactional 註解標註的方法中不要出現網絡調用、比較耗時的處理程序,由於,事務中數據庫鏈接是不會釋放的,若是每一個事務的處理時間都很是長,那麼寶貴的數據庫鏈接資源將很快被耗盡。
相關文章
相關標籤/搜索