spring事務使用+常見出錯解決方案
1 spring事務處理
spring事務配置有多種方式,這裡以全註解方式進行介紹。
1.1 前提
spring專案已正常跑通;maven專案;
1.2 spring配置檔案修改
增加事務管理器:
<!--TransactionManager定義 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property
</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.RuntimeException或java.lang.Error類衍生出來的。例如:nullpointexception,IndexOutOfBoundsException就屬於runtime exception
4. Exception:定義方法時必須宣告所有可能會丟擲的exception;在呼叫這個方法時,必須捕獲它的checked exception,不然就得把它的exception傳遞下去;exception是從java.lang.Exception類衍生出來的。例如:IOException,SQLException就屬於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. }