1. 程式人生 > >【本人禿頂程式設計師】SpringMVC+Mybatis+事務回滾+異常封裝返回

【本人禿頂程式設計師】SpringMVC+Mybatis+事務回滾+異常封裝返回

←←←←←←←←←←←← 快,點關注!

問題的背景:

  1. 使用dubbo搭建分散式服務架構,service的實現,採用SpringMVC4.1.6+MyBatis3.2.8。
  2. 為了少維護一個維度,擬對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資料庫教學視訊及架構學習思維導圖