Spring的AOP事務管理默認是針對unchecked exception回滾。
也就是默認對RuntimeException()異常極其子類進行事務回滾。
Exception作為基類,下面還分checked exception和unchecked exception。如果客戶端可以通過其他的方法恢復異常,那么這種異
常就是checked exception;如果客戶端對出現的這種異常無能為力,那么這種異常就是Unchecked exception;簡單來說,繼承於
RuntimeException的都是unchecked exception。
Error:
1.總是不可控制的(unchecked)
2.經常用來用於表示系統錯誤或低層資源的錯誤
3.如何可能的話,應該在系統級被捕捉
Exception:
1.可以是可被控制(checked) 或不可控制的(unchecked)
2.表示一個由程序員導致的錯誤
3.應該在應用程序級被處理
Java 中定義了兩類異常:
1) Checked exception: 這類異常都是Exception的子類。異常的向上拋出機制進行處理,假如子類可能產生A異常,那么在父類中
也必須throws A異常。可能導致的問題:代碼效率低,耦合度過高。
2) Unchecked exception: 這類異常都是RuntimeException的子類,雖然RuntimeException同樣也是Exception的子類,但是它們是
非凡的,它們不能通過client code來試圖解決,所以稱為Unchecked exception 。
解決辦法:
1.在針對事務的類中拋出RuntimeException異常,而不是拋出Exception。
2.在txAdive中增加rollback-for,里面寫自己的exception,例如自己寫的exception為
com.cn.untils.exception.***Exception
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" rollback-for="com.cn.untils.exception.***Exception"/>
</tx:attributes>
</tx:advice>
或者
定義不會滾的異常
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="update*" no-rollback-for="IOException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
spring事務回滾.默認情況,unchecked異常,即運行時異常runntimeException回滾事務;checked異常,即Exception可try{}捕獲的不會回滾.當然也可配置spring參數讓其回滾.
試驗方法:
寫一個單元測試,調用一個service層方法(發生對數據庫進行寫操作的方法--insert、update、delete)即可.
試驗過程:
定義一個service方法如下:
public SMSTiming createSMSTiming(SMSTiming smsTiming){
SMSTiming s= this.getSmsTimingDAO().createSMSTiming(smsTiming);
return s;
}
定義二個異常(先默認配置TestException為Spring事務回滾異常):
publicclass MyTestException extends Exception
publicclass TestException extends Exception
注意看下:每次這個方法的不同處(拋出的異常不同)。
測試1:
public SMSTiming createSMSTiming(SMSTiming smsTiming){
SMSTiming s= this.getSmsTimingDAO().createSMSTiming(smsTiming);
int i = 4/0; //人為產生異常(實際這里拋出了ArithmeticException運行異常)
return s;
}
測試1結果:會事務回滾----數據庫中未插入新數據。
測試2:
public SMSTiming createSMSTiming(SMSTiming smsTiming) throws Exception{//受檢異常(非運行異常)必須拋出
SMSTiming s= this.getSmsTimingDAO().createSMSTiming(smsTiming);
try{
int i = 4/0; //人為產生異常
}catch(Exception e){
thrownew Exception ("");//拋出Exception異常
}
return s;
}
測試2結果:不會事務回滾----數據庫中插入新數據。
測試3:
public SMSTiming createSMSTiming(SMSTiming smsTiming) throws RuntimeException{//運行異常(非受檢異常)可以不拋出
SMSTiming s= this.getSmsTimingDAO().createSMSTiming(smsTiming);
try{
int i = 4/0; //人為產生異常
}catch(Exception e){
thrownewRuntimeException("");//拋出RuntimeException異常
}
return s;
}
測試3結果:會事務回滾----數據庫中未插入新數據
測試4:
public SMSTiming createSMSTiming(SMSTiming smsTiming) throws TestException{//受檢異常(非運行異常)必須拋出
SMSTiming s= this.getSmsTimingDAO().createSMSTiming(smsTiming);
try{
int i = 4/0; //人為產生異常
}catch(Exception e){
thrownewTestException("");//拋出TestException異常
}
return s;
}
測試4結果:會事務回滾----數據庫中未插入新數據。
測試5:
public SMSTiming createSMSTiming(SMSTiming smsTiming) throws MyTestException{//受檢異常(非運行異常)必須拋出
SMSTiming s= this.getSmsTimingDAO().createSMSTiming(smsTiming);
try{
int i = 4/0; //人為產生異常
}catch(Exception e){
thrownewMyTestException("");//拋出MyTestException異常
}
return s;
}
測試5結果:不會事務回滾----數據庫中插入新數據。
測試6:
public SMSTiming createSMSTiming(SMSTiming smsTiming) throws MyTestException{//受檢異常(非運行異常)必須拋出 (注意:此時spring指定配置此異常回滾)
SMSTiming s= this.getSmsTimingDAO().createSMSTiming(smsTiming);
try{
int i = 4/0; //人為產生異常
}catch(Exception e){
thrownewMyTestException("");//拋出MyTestException異常
}
return s;
}
測試6結果:會事務回滾----數據庫中未插入新數據。
試驗總結:
測試1、測試3、測試4、測試6會進行事務回滾;測試2、測試5不會進行事務回滾。
為什么會這樣?因為是異常的類型(受檢異常、運行時異常)不同或使用了Spring的rollback-for配置。
測試1和測試3是因為拋出了運行時異常,會事務回滾。
測試4和測試5、測試6分別拋出受檢異常TestException、MyTestException,那為什么測試4和測試6會事務回滾呢?
因為是我們在Spring事務配置中指定了此異常(指定rollback-for)。
最近遇到了事務不回滾的情況,我還考慮說JPA的事務有bug? 我想多了.......
為了打印清楚日志,很多方法我都加tyr catch,在catch中打印日志。但是這邊情況來了,當這個方法異常時候 日志是打印了,但是加的事務卻沒有回滾。
例:
類似這樣的方法不會回滾 (一個方法出錯,另一個方法不會回滾) :
- if(userSave){
- try {
- userDao.save(user);
- userCapabilityQuotaDao.save(capabilityQuota);
- } catch (Exception e) {
- logger.info("能力開通接口,開戶異常,異常信息:"+e);
- }
- }
下面的方法回滾(一個方法出錯,另一個方法會回滾):
- if(userSave){
- try {
- userDao.save(user);
- userCapabilityQuotaDao.save(capabilityQuota);
- } catch (Exception e) {
- logger.info("能力開通接口,開戶異常,異常信息:"+e);
- throw new RuntimeException();
- }
- }
或者:
- if(userSave){
- try {
- userDao.save(user);
- userCapabilityQuotaDao.save(capabilityQuota);
- } catch (Exception e) {
- logger.info("能力開通接口,開戶異常,異常信息:"+e);
- TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
- }
- }
為什么不會滾呢??是對Spring的事務機制就不明白。!!
默認spring事務只在發生未被捕獲的 runtimeexcetpion時才回滾。
spring aop 異常捕獲原理:被攔截的方法需顯式拋出異常,並不能經任何處理,這樣aop代理才能捕獲到方法的異常,才能進行回滾,默認情況下aop只捕獲runtimeexception的異常,但可以通過
配置來捕獲特定的異常並回滾
換句話說在service的方法中不使用try catch 或者在catch中最后加上throw new runtimeexcetpion(),這樣程序異常時才能被aop捕獲進而回滾
解決方案:
方案1.例如service層處理事務,那么service中的方法中不做異常捕獲,或者在catch語句中最后增加throw new RuntimeException()語句,以便讓aop捕獲異常再去回滾,並且在service上層(webservice客戶端,view層action)要繼續捕獲這個異常並處理
方案2.在service層方法的catch語句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();語句,手動回滾,這樣上層就無需去處理異常(現在項目的做法)
Spring框架的事務基礎架構代碼將默認地 只 在拋出運行時和unchecked exceptions時才標識事務回滾。 也就是說,當拋出一個RuntimeException
或其子類例的實例時。(Errors
也一樣 - 默認地 - 標識事務回滾。)從事務方法中拋出的Checked exceptions將 不 被標識進行事務回滾