1. 程式人生 > >spring事務使用+常見出錯解決方案

spring事務使用+常見出錯解決方案

1      spring事務處理

spring事務配置有多種方式,這裡以全註解方式進行介紹。

1.1     前提

spring專案已正常跑通;maven專案;

1.2     spring配置檔案修改

增加事務管理器:

<!--TransactionManager定義 -->

       <bean id="transactionManager"

              class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

              <property

name="dataSource" ref="dataSource1" />

       </bean>

       <bean id="transactionTemplate"

              class="org.springframework.transaction.support.TransactionTemplate">

              <property name="transactionManager" ref="transactionManager" />

       </bean>

       <!-- enables scanning for @Transactionalannotations -->

       <tx:annotation-driven transaction-manager="transactionManager" />

<tx:annotation-driven>一共有四個屬性如下,

Ø  mode:指定Spring事務管理框架建立通知bean的方式。可用的值有proxy和aspectj。前者是預設值,表示通知物件是個JDK代理;後者表示Spring AOP會使用AspectJ建立代理

Ø  proxy-target-class:如果為true,Spring將建立子類來代理業務類;如果為false,則使用基於介面的代理。(如果使用子類代理,需要在類路徑中新增CGLib.jar類庫)

Ø  order:如果業務類除事務切面外,還需要織入其他的切面,通過該屬性可以控制事務切面在目標連線點的織入順序。

Ø  transaction-manager:指定到現有的PlatformTransaction Manager bean的引用,通知會使用該引用

1.3     增加@Transactional註解

在需要的函式上增加:

1.       @Transactional 

2.       public void test() throws Exception {  

3.            doDbStuff1();  

4.            doDbStuff2();//假如這個操作資料庫的方法會丟擲runtimeexception,現在方法doDbStuff1()對資料庫的操作會回滾。  

5.       }  

1.4     注意事項

1.        在需要事務管理的地方加@Transactional註解。@Transactional 註解可以被應用於介面定義和介面方法、類定義和類的 public 方法上。

2.        @Transactional註解只能應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設定。

3.        注意僅僅 @Transactional 註解的出現不足於開啟事務行為,它僅僅是一種元資料。必須在配置檔案中使用配置元素,才真正開啟了事務行為。

4.        Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 註解,而不要使用在類所要實現的任何介面上。在介面上使用 @Transactional 註解,只能當你設定了基於介面的代理時它才生效。因為註解是 不能繼承 的,這就意味著如果正在使用基於類的代理時,那麼事務的設定將不能被基於類的代理所識別,而且物件也將不會被事務代理所包裝。

5.        @Transactional 的事務開啟,是基於介面的或者是基於類的代理被建立。所以在同一個類中一個方法呼叫另一個方法是有事務的方法,事務是不會起作用的

6.        Spring使用宣告式事務處理,預設情況下,如果被註解的資料庫操作方法中發生了unchecked異常,所有的資料庫操作將rollback;如果發生的異常是checked異常,預設情況下資料庫操作還是會提交的。

1.5     常用引數說明

 參 數 名 稱

功 能 描 述

readOnly

該屬性用於設定當前事務是否為只讀事務,設定為true表示只讀,false則表示可讀寫,預設值為false。例如:@Transactional(readOnly=true)

rollbackFor

該屬性用於設定需要進行回滾的異常類陣列,當方法中丟擲指定異常陣列中的異常時,則進行事務回滾。例如:

指定單一異常類:@Transactional(rollbackFor=RuntimeException.class)

指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class})

參 數 名 稱

功 能 描 述

rollbackForClassName

該屬性用於設定需要進行回滾的異常類名稱陣列,當方法中丟擲指定異常名稱陣列中的異常時,則進行事務回滾。例如:

指定單一異常類名稱:@Transactional(rollbackForClassName="RuntimeException")

指定多個異常類名稱:@Transactional(rollbackForClassName={"RuntimeException","Exception"})

noRollbackFor

該屬性用於設定不需要進行回滾的異常類陣列,當方法中丟擲指定異常陣列中的異常時,不進行事務回滾。例如:

指定單一異常類:@Transactional(noRollbackFor=RuntimeException.class)

指定多個異常類:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})

noRollbackForClassName

該屬性用於設定不需要進行回滾的異常類名稱陣列,當方法中丟擲指定異常名稱陣列中的異常時,不進行事務回滾。例如:

指定單一異常類名稱:@Transactional(noRollbackForClassName="RuntimeException")

指定多個異常類名稱:

@Transactional(noRollbackForClassName={"RuntimeException","Exception"})

propagation

該屬性用於設定事務的傳播行為,具體取值可參考表6-7。

例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)

isolation

該屬性用於設定底層資料庫的事務隔離級別,事務隔離級別用於處理多事務併發的情況,通常使用資料庫的預設隔離級別即可,基本不需要進行設定

timeout

該屬性用於設定事務的超時秒數,預設值為-1表示永不超時

1.6     exception不回滾解決方案

1.6.1  原因

1.        Checked異常必須被顯式地捕獲或者傳遞,如Basic try-catch-finally Exception Handling一文中所說。而unchecked異常則可以不必捕獲或丟擲。

2.        Checked異常繼承java.lang.Exception類。Unchecked異常繼承自java.lang.RuntimeException類。

3.        Runtime Exception在定義方法時不需要宣告會丟擲runtime exception在呼叫這個方法時不需要捕獲這個runtime exception runtime exception是從java.lang.RuntimeExceptionjava.lang.Error類衍生出來的。例如:nullpointexceptionIndexOutOfBoundsException就屬於runtime exception 

4.        Exception:定義方法時必須宣告所有可能會丟擲的exception在呼叫這個方法時,必須捕獲它的checked exception,不然就得把它的exception傳遞下去;exception是從java.lang.Exception類衍生出來的。例如:IOExceptionSQLException就屬於Exception

預設情況下,如果被註解的資料庫操作方法中發生了unchecked異常,所有的資料庫操作將rollback;如果發生的異常是checked異常,預設情況下資料庫操作還是會提交的。而Exception是checked異常,所以不會回滾。

1.6.2  解決方案

引數增加如下,即可:

6.       @Transactional(rollbackFor = { Exception.class })  

7.       public void test() throws Exception {  

8.            doDbStuff1();  

9.            doDbStuff2();//假如這個操作資料庫的方法會丟擲異常,現在方法doDbStuff1()對資料庫的操作會回滾。  

10.    }  

1.7     spring +springmvc 註解事務無效解決方案

1.7.1  原因

SpringMVC啟動時的配置檔案,包含元件掃描、url對映以及設定freemarker引數,讓spring不掃描帶有@Service註解的類。

為什麼要這樣設定?因為servlet-context.xml與service-context.xml不是同時載入,如果不進行這樣的設定,那麼,spring就會將所有帶@Service註解的類都掃描到容器中,等到載入service-context.xml的時候,會因為容器已經存在Service類,使得cglib將不對Service進行代理,直接導致的結果就是在service-context中的事務配置不起作用,發生異常時,無法對資料進行回滾。

1.7.2  解決方案

1.       spring mvc 自動掃描註解的時候,不去掃描@Service

1.       <context:component-scanbase-package= "org.cn.xxx">

2.       <context:exclude-filtertype ="annotation" expression="org.springframework.stereotype.Service" />

3.       </context:component-scan>

2. spring 自動掃描註解的時候,不去掃描@Controller

1.       <context:component-scanbase-package ="org.cn.xxx>

2.       <context:exclude-filtertype ="annotation" expression="org.springframework.stereotype.Controller" />

3.       </context:component-scan>

1.8     try catch後事務不回滾解決方案

在Spring的配置檔案中,如果資料來源的defaultAutoCommit設定為True了,那麼方法中如果自己捕獲了異常,事務是不會回滾的,如果沒有自己捕獲異常則事務會回滾。

情況1:如果沒有在程式中手動捕獲異常,正常回滾

1.       @Transactional(rollbackFor = { Exception.class })  

2.       public void test() throws Exception {  

3.            doDbStuff1();  

4.            doDbStuff2();//假如這個操作資料庫的方法會丟擲異常,現在方法doDbStuff1()對資料庫的操作會回滾。  

5.       }  

情況2:如果在程式中自己捕獲了異常,不會回滾

1.       @Transactional(rollbackFor = { Exception.class })  

2.       public void test() {  

3.            try {  

4.               doDbStuff1();  

5.               doDbStuff2();//假如這個操作資料庫的方法會丟擲異常,現在方法doDbStuff1()對資料庫的操作不會回滾。  

6.            } catch (Exception e) {  

7.                  e.printStackTrace();     

8.            }  

9.       }  

1.8.1  原因

springaop異常捕獲原理:被攔截的方法需顯式丟擲異常,並不能經任何處理,這樣aop代理才能捕獲到方法的異常,才能進行回滾

現在如果我們需要手動捕獲異常,並且也希望拋異常的時候能回滾腫麼辦呢?以下給出3種解決方案,供大家參考,專案中使用解決方案3。

1.8.2  解決方案1-不使用try catch

@Transactional所在函式不進行try catch捕獲,而是放到上層函式進行異常捕獲。

比如@Transactional放在service層,我們在service層不進行異常處理,只丟擲,而在controller層進行異常捕獲。

1.8.3  解決方案2-在catch中throw

catch後再throw,顯示回滾

1.       @Transactional(rollbackFor = { Exception.class })  

2.       public void test() {  

3.            try {  

4.               doDbStuff1();  

5.               doDbStuff2();//假如這個操作資料庫的方法會丟擲異常,現在方法doDbStuff1()對資料庫的操作不會回滾。  

6.            } catch (Exception e) {  

7.                  e.printStackTrace();    

8.                   throw new Exception(“error”);

9.            }  

10.    }  

1.8.4  解決方案3-手動回滾

TransactionAspectSupport手動回滾事務:

1.       @Transactional(rollbackFor = { Exception.class })  

2.       public void test() {  

3.            try {  

4.               doDbStuff1();  

5.               doDbStuff2();  

6.            } catch (Exception e) {  

7.                 e.printStackTrace();     

8.                 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//就是這一句了,加上之後,如果doDbStuff2()拋了異常,                                                                                       //doDbStuff1()是會回滾的  

9.            }  

10.    }  

1.9     參考文獻