【本人禿頂程式設計師】SpringMVC+Mybatis+事務回滾+異常封裝返回
←←←←←←←←←←←← 快,點關注!
問題的背景:
- 使用dubbo搭建分散式服務架構,service的實現,採用SpringMVC4.1.6+MyBatis3.2.8。
- 為了少維護一個維度,擬對service介面進行通用性定義,即讓業務的變化,不影響已定義的service介面。
最終Service的方法簽名定義如下(示例):
public ServiceResult addProduct(ServiceParam param)
其中ServiceResult定義如下:
public class ServiceResult<T> { private T result; /** * 錯誤返回code */ private int retCode; public int getRetCode() { return retCode; } public void setRetCode(int retCode) { this.retCode = retCode; } public T getResult() { return result; } public void setResult(T result) { this.result = result; } }
其中ServiceParam定義如下:
public class ServiceParam<T> {
private T param;
public T getParam() {
return param;
}
public void setParam(T param) {
this.param = param;
}
}
此方法簽名保證遠端呼叫兩端的業務變化,不會對此介面造成影響。實現了運維維度減一。
其中,ServiceResult封裝了所有的業務和執行異常,將之轉化為errorCode返回。如此便減少了異常的遠端傳輸的消耗,同時隔離了呼叫兩端的異常耦合,也避免了呼叫方在巢狀的異常捕捉塊裡進行業務處理的尷尬。
但是,在實際的框架搭建中,卻發現了這樣的一個問題,addProduct方法的事務,如果通過Spring來管理,則必須要拋異常來進行事務的回滾。如果方法拋了異常(對呼叫者來說,就是遠端異常),則不能滿足我們把異常轉化到errorCode來輸出的目的。
因此,我們必須想辦法去把異常捕獲轉化成errorCode,同時又不能影響事務回滾。經過多次嘗試,終於圓滿解決該問題。特此記錄分享於此。
下面是我的思路及解決辦法:
既然使用的是Spring來管理事務,則就用AOP來攔截相關Service方法,在事務回滾過後,繼續捕捉到異常,對該異常進行轉化輸出。
第一步,建立AOP攔截類
@Aspect @Component @Order(0) public class ExceptionAspect { /** * 配置切入點,該方法無方法體,主要為方便同類中其他方法使用此處配置的切入點 */ @Pointcut("execution(* cn.xx.dubbo.security.service..*(..))") public void aspect() { } @Around("aspect()") public ServiceResult around(JoinPoint joinPoint) { System.out.println("===============START"); ServiceResult result; try { //呼叫攔截的方法主體 result = (ServiceResult) ((ProceedingJoinPoint) joinPoint).proceed(); } catch (Throwable e) { System.out.println("出現了異常:" + e.getMessage()); result = new ServiceResult(); result.setResult("exception:" + e.getMessage()); result.setRetCode(-1); } System.out.println("===============END"); return result; } }
在該類中,定義切面執行的順序Order(0),即最外層。
第二步,使用註解對service服務方法進行事務定義,並在service方法丟擲業務異常,以便使得事務回滾。
@Override
@Transactional
public ServiceResult addProduct(ServiceParam<Product> param)
throws Exception {
Product product = param.getParam();
int rlt = biz.addProduct(product);
if(rlt == 0)
throw new Exception("操作失敗!");
else {
ServiceResult<Integer> result = new ServiceResult();
result.setResult(rlt);
return result;
}
}
第三步,修改Spring配置檔案
使用註解式事務宣告,加入order屬性,使其大於0,則優先於自定義的AOP類執行事務
<tx:annotation-driven transaction-manager="transactionManager"
proxy-target-class="true" order="10"/>
最後用Junit測試該service方法,即便該方法拋異常,最外層的ExceptionAspect 也會攔截到該異常,在事務正確回滾之後,把異常轉化為errorCode,封裝到ServiceResult中成功返回,改造成功!
歡迎大家加入粉絲群:963944895,群內免費分享Spring框架、Mybatis框架SpringBoot框架、SpringMVC框架、SpringCloud微服務、Dubbo框架、Redis快取、RabbitMq訊息、JVM調優、Tomcat容器、MySQL資料庫教學視訊及架構學習思維導圖