Spring - service層與存儲過程的事務回滾

在service中,難免會遇到service調用service或者存儲過程的時候。有時候,明明開啓了事務(xml配置或者註解事務),代碼卻沒有正常回滾。


【案例一】:service調用service

如,service A的方法A調用了service B的方法B,service C的方法C。該三個service對應ABC接口方法均使用註解事務(注意事務是在接口方法上,而不是實現類上面)。

methodA(){
    serviceB.B();
    異常;
    serviceC.C();
}
  • 1
  • 2
  • 3
  • 4
  • 5

正常理解,BC都進行回滾。@Transactional 默認的事務propagation="REQUIRED"。也就是如果當前有事務,那麼會使用同一個事務,應該都進行回滾。

爲何,B的數據提交了?

這裏需要說明一點:

Spring框架的事務基礎架構代碼將默認地只在拋出運行時和unchecked exceptions時才標識事務回滾。

也就是說,當拋出一個RuntimeException 或其子類例的實例時(Errors 也一樣) -默認地標識事務回滾。

從事務方法中拋出的Checked exceptions將不被標識進行事務回滾。

故,需要判斷你拋出異常的類型。

可以如下設置,無論何種異常,均進行回滾:

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

如果還不行,要看一下你的xml配置。這裏首先說明一下@Transactional註解。


【@Transactional】

這裏寫圖片描述

@Transactional 可以作用於接口、接口方法、類以及類方法上。當作用於類上時,該類的所有 public 方法將都具有該類型的事務屬性,同時,我們也可以在方法級別使用該標註來覆蓋類級別的定義。

雖然 @Transactional 註解可以作用於接口、接口方法、類以及類方法上,但是 Spring 建議不要在接口或者接口方法上使用該註解,因爲這只有在使用基於接口的代理時它纔會生效。

另外, @Transactional 註解應該只被應用到 public 方法上,這是由 Spring AOP 的本質決定的。如果你在 protected、private 或者默認可見性的方法上使用 @Transactional 註解,這將被忽略,也不會拋出任何異常。

默認情況下,只有來自外部的方法調用纔會被AOP代理捕獲,也就是,類內部方法調用本類內部的其他方法並不會引起事務行爲,即使被調用方法使用@Transactional註解進行修飾。

這裏着重注意兩點,public方法和基於接口代理。

故,如果事務已經使用了上述配置(所有異常都進行回滾),但還是未成功。那麼需要查看一下你的方法修飾符合XML配置。

xml配置如下:

<!-- 配置spring的PlatformTransactionManager,名字爲默認值 -->  
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean>  

    <!-- 開啓事務控制的註解支持 -->  
    <tx:annotation-driven transaction-manager="transactionManager"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

注意,不要配置如下:

<tx:annotation-driven transaction-manager="transactionManager"   proxy-target-class="true"/>

<aop:config proxy-target-class="true">
  • 1
  • 2
  • 3

說明如下:

proxy-target-class屬性值決定是基於接口的還是基於類的代理被創建。

如果proxy-target-class 屬性值被設置爲true,那麼基於類的代理將起作用(這時需要cglib庫)。

如果proxy-target-class屬值被設置爲false或者這個屬性被省略,那麼標準的JDK 基於接口的代理。

如果不給出 proxy-target-class,就按 proxy-target-class=“false”對待,也即是按JDK proxy來處理的。

案例一開始說明,事務是在接口的方法上面,那麼不要開啓類的代理!!! 
如果開啓了類的代理,請將事務加在類的方法上面!!!

綜上,service調用service事務不起作用從以下幾個方面判斷:

① 方法是否public;

② rollbackFor={Exception.class};

③ 根據事務是基於類方法還是接口方法驗證xml是否開啓了類代理;

④ 如果是xml配置攔截式事務,驗證正確性;

示例如下:

<!-- 事物切面配置 -->
    <tx:advice id="advice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="update*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
            <tx:method name="insert" propagation="REQUIRED" read-only="false"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="testService" expression="execution (* com.baobao.service.MyBatisService.*(..))"/>
        <aop:advisor advice-ref="advice" pointcut-ref="testService"/>
    </aop:config>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

⑤ 如果使用註解事務,驗證xml配置;

示例如下:

<!-- 配置spring的PlatformTransactionManager,名字爲默認值 -->  
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean>  

    <!-- 開啓事務控制的註解支持 -->  
    <tx:annotation-driven transaction-manager="transactionManager"/>

如果事務是基於類代理的,如下:

<tx:annotation-driven transaction-manager="transactionManager"   proxy-target-class="true"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

【案例二】:service調用存儲過程

如serviceA的A方法調用serviceB的B方法,而B方法只是調用了一個存儲過程。在該存儲過程中開啓了一個事務。AB方法都開啓了事務(事務加在了接口方法上)。

僞代碼如下:

methodA(){
    serviceB.B();
    異常;
}
  • 1
  • 2
  • 3
  • 4

不幸的事情又來了,拋出了異常但是B卻提交了數據。

說明如下:

事務分爲兩種,自動事務和手動事務。像直接在數據庫update,數據自動事務。而程序中不管使用哪種方式開啓了事務,就屬於手動事務。

事務有七種傳播屬性,當開啓手動事務時,默認或者指定一種屬性。而存儲過程的事務屬性屬於PROPAGATION_NEW。

即:propagation=Propagation.NEW

也就是說,存儲過程一旦開啓了事務,只要存儲過程正常執行完,就會刷數據進入數據表。即使當前service下面拋出了異常,也不會進行回滾!!!但是不影響service的自身事務

示例如下:

methodA(){
    call procP();
    異常;
    serviceB.B();
}

methodC(){
    methodA();
    異常;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

這裏每個接口方法和存儲過程都開啓了事務。調用menthodC,那麼存儲過程P正常執行完將會刷入數據表,而其他對數據表的操作將會回滾。

故,如果service調用存儲過程時:

如果service只有存儲對數據表進行了操作,那麼可以在存儲過程裏面開啓事務。service接口方法可開可不開。

如果service中除了存儲過程外還有其他update或者delete操作,那麼在servcie上一定要開啓事務(基於接口方法或者類方法都可以),存儲過程請勿開啓事務。這樣存儲過程使用service的事務。


【REQUIRES_NEW和PROPAGATION_NESTED】

ROPAGATION_REQUIRED_NEW 啓動一個新的, 不依賴於環境的 “內部” 事務. 這個事務將被完全 commited 或 rolled back 而不依賴於外部事務, 它擁有自己的隔離範圍, 自己的鎖, 等等. 當內部事務開始執行時, 外部事務將被掛起, 內務事務結束時, 外部事務將繼續執行.

另一方面, PROPAGATION_NESTED 開始一個 “嵌套的” 事務, 它是已經存在事務的一個真正的子事務. 嵌套事務開始執行時, 它將取得一個 savepoint. 如果這個嵌套事務失敗, 我們將回滾到此 savepoint。

嵌套事務是外部事務的一部分, 只有外部事務結束後它纔會被提交。

由此可見, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於: 
PROPAGATION_REQUIRES_NEW 完全是一個新的事務, 而 PROPAGATION_NESTED 則是外部事務的子事務, 如果外部事務 commit, 潛套事務也會被 commit, 這個規則同樣適用於 roll back.

 

經測試,把所有調用到的存儲過程中的事務全部註釋掉,通通把事務管理交由spring管理就ok!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章