在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!