Transaction rolled back because it has been marked as rollback-only 原因 和解決方案


產生原因  ,

1 serviceA 調用 serviceB 然后 B  拋出異常 ,B 所在的 事物 回滾,B 把當前可寫 事物標記成 只讀事物 ,

2 如果 A 和B 是在 同一個事物環境,並且 A 抓了 B 拋出的異常,沒有和 B 一起回滾,

3 然后 A 方法 完成,把當前事物 當成 寫事物提交。就會出上面的問題。

 

 

 

上代碼:

 

 

 

 

 

 

 

解釋: 可以看出  上面代碼 問題有點多, 方法 A  是 沒有 事物環境的,也就是說 調用方法A 的 前面的方法如果有事物環境, 方法A 就 依賴前面的事物環境, 沒有 方法A 就 是以非事物的方式執行( 級聯操作 ,如果 在非事物 環境執行,就不能級聯回滾,正常必須要在事物環境 ),然后 就是 隨意的抓異常,讓本來應該回滾的 事物沒有回滾,依舊在向下執行。

 

正確的處理方法:  如果是級聯操作,那么 應該處於同一個 事物環境,並且不應該 隨意的抓異常,只有 自己 能處理 ,並且不影響事物回滾的異常才能抓,別的異常統統不允許抓。並且 默認的 回滾需要 runtimexception ,所以只能拋出這個異常的子類 ,如果 出現別的異常, 非 runtimexception  那么 應該抓了 拋出 runtimexception 。

 

另外 說一句 spring有完善的全局 異常處理體系, 正常來說不需要 隨意的 try 異常。 我們需要處理的只有 非 runtimexception ,別的異常直接跑就是了。

 

 

 

 

 

 

推薦的異常處理方法:

1  定義 全局異常處理,然后根據 不同異常 ,返回不同的 提示信息。

全局異常處理類:

package com.hs.backend.controller.exception;

import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import com.hs.commons.exception.CanRedirectExcetion;
import com.hs.commons.utils.ClassUtils;

/**
 * 統一異常處理
 * 
 * @author ZHANGYUKUN
 *
 */
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {

    Logger logger = LoggerFactory.getLogger(GlobalExceptionResolver.class);
    
    @Autowired
    RequestMappingHandlerMapping requestMappingHandlerMapping;

    /**
     * 異常前綴
     */
    private String prefix = "/global/exception/";

    /**
     * 異常后綴
     */
    private String suffix = "Result";

    /**
     * 自定義異常列表
     * 
     */
    //private List<Class<?>> exceptionClsList = new ArrayList<>();

    /**
     * 子包名
     */
    //private String subPackage = "exception";

    /**
     * 默認的處理結果
     */
    private String defaultResult = "unknownException";

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
                                         Exception exception) {
        ModelAndView mv = new ModelAndView();
        mv.setViewName(getForwardViewName(getViewName(defaultResult)));
        mv.addObject("exception", exception);
        Class<?> cls = exception.getClass();
        String viewName = getViewName(ClassUtils.getObjectName(cls.getSimpleName()));
        if (CanRedirectExcetion.class.isInstance(exception)) {

            Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
            Set<RequestMappingInfo> set = map.keySet();
            for (RequestMappingInfo requestMappingInfo : set) {
                if (!requestMappingInfo.getPatternsCondition().getMatchingPatterns( viewName ).isEmpty()) {
                    mv.setViewName(getForwardViewName(getViewName(ClassUtils.getObjectName(cls.getSimpleName()))));
                    return mv;
                }
            }

        }
        System.out.println( exception.toString() );
        return mv;
    }

    /**
     * 通過 異常對象名 得到 異常視圖名
     * @param exceptionObjectName
     * @return
     */
    private String getViewName(String exceptionObjectName) {
        return prefix + exceptionObjectName + suffix;
    }
    
    /**
     * 得到 forward 格式 的viewName
     * @param viewName
     * @return
     */
    private String getForwardViewName(String viewName) {
        return "forward:" + viewName;
    }

}

 

異常信息返回:  上面的 異常處理類會 調用下面這個類的不同方法 返回 前端 不同錯誤碼 ,並且 統一格式的 信息。

package com.hs.backend.controller.exception;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.hs.backend.exception.GlobalExceptionResolver;
import com.hs.common.result.CommonErrorInfo;
import com.hs.common.result.CommonResult;
import com.hs.commons.exception.AlreadyExistRecord;
import com.hs.commons.exception.ApiInvokingException;
import com.hs.commons.exception.DataErrorException;
import com.hs.commons.exception.NoPermissionException;
import com.hs.commons.exception.NoTokenErrorException;
import com.hs.commons.exception.ParameterErrorException;
import com.hs.commons.exception.TokenExpiredException;
import com.hs.commons.exception.UnLoginException;

import springfox.documentation.annotations.ApiIgnore;

/**
 * 定義異常的返回處理策略,這個路勁不應該添加熱河權限限制
 *
 * @author ZHANGYUKUN
 */
@ApiIgnore
@RestController
@RequestMapping("global")
public class GlobalController {

    private static Logger logger = LoggerFactory.getLogger(GlobalExceptionResolver.class);

    /**
     * 未知異統一處理
     *
     * @return
     */
    @RequestMapping("exception/unknownExceptionResult")
    public CommonResult<Void> unknownExceptionResult() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Exception exception =  (Exception) request.getAttribute("exception");
        
        Throwable throwable = null;
        if (logger.isErrorEnabled()) {
            logger.error("未知異常發生了:", exception);
            throwable =  findFinalCause( exception );;
        }
        
        //如果取不到異常堆站里面的 信息 ,就向前端提示未知異常
        String message = "未知的異常";
        if( throwable.getMessage() != null ) {
            message = throwable.getMessage();
        }
        return CommonResult.getFaiInstance(CommonErrorInfo.code_3001, message);
    }
    
    /**
     * 參錯誤統一返回
     *
     * @return
     */
    @RequestMapping("exception/parameterErrorExceptionResult")
    public CommonResult<Void> parameterErrorExceptionResult() {
         HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
         ParameterErrorException exception =  (ParameterErrorException)request.getAttribute("exception");
        
        return CommonResult.getFaiInstance(CommonErrorInfo.code_1001,  exception.getMessage() );
    }

    /**
     * 沒有權限統一返回
     * @return
     */
    @RequestMapping("exception/noPermissionExceptionResult")
    public  CommonResult<Void>  noPermissionException() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        NoPermissionException exception =  (NoPermissionException)request.getAttribute("exception");
        return CommonResult.getFaiInstance( CommonErrorInfo.code_2001 , exception.getMessage() == null ? "沒有權限" : exception.getMessage());
    }

    /**
     * 未登錄統一返回
     *
     * @return
     */
    @RequestMapping("exception/unLoginExceptionResult")
    public CommonResult<Void> unLoginExceptionResult() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        UnLoginException exception =  (UnLoginException)request.getAttribute("exception");

        return CommonResult.getFaiInstance(CommonErrorInfo.code_4001, exception.getMessage());
    }
    
    
   /**
    * 已存在的記錄錯誤
    * @return
    */
    @RequestMapping("exception/alreadyExistRecordResult")
    public CommonResult<Void> alreadyExistRecordResult() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        AlreadyExistRecord exception =  (AlreadyExistRecord)request.getAttribute("exception");
        
        return CommonResult.getFaiInstance(CommonErrorInfo.code_5001,  exception.getMessage() );
    }

    /**
     * 沒有token
     * @return
     */
    @RequestMapping("exception/noTokenErrorExceptionResult")
    public CommonResult<Void> noTokenErrorExceptionResult() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        NoTokenErrorException exception =  (NoTokenErrorException)request.getAttribute("exception");

        return CommonResult.getFaiInstance(CommonErrorInfo.code_2101,  exception.getMessage() );
    }

    /**
     * token過期
     * @return
     */
    @RequestMapping("exception/tokenExpiredExceptionResult")
    public CommonResult<Void> tokenExpiredExceptionResult() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        TokenExpiredException exception =  (TokenExpiredException)request.getAttribute("exception");

        return CommonResult.getFaiInstance(CommonErrorInfo.code_2102,  exception.getMessage() );
    }

    /**
     * 數據錯誤
     * @return
     */
    @RequestMapping("exception/dataErrorExceptionResult")
    public CommonResult<Void> dataErrorException() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        DataErrorException exception =  (DataErrorException)request.getAttribute("exception");
        
        return CommonResult.getFaiInstance(CommonErrorInfo.code_6001,  exception.getMessage() );
    }
    
    /**
     * 訪問頻率過高
     * @return
     */
    @RequestMapping("exception/requestFrequencyExceptionResult")
    public CommonResult<Void> requestFrequencyException() {
        return CommonResult.getFaiInstance(CommonErrorInfo.code_8001,  "訪問頻率過高" );
    }
    
    /**
     * api調用異常錯誤
     * @return
     */
    @RequestMapping("exception/apiInvokingExceptionResult")
    public CommonResult<Void> apiInvokingException() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        ApiInvokingException exception =  (ApiInvokingException)request.getAttribute("exception");
        
        return CommonResult.getFaiInstance(CommonErrorInfo.code_9001,  exception.getMessage() );
    }
    
    
    
    /**
     * 得到最根上的異常
     * 
     * @param throwable
     * @return
     */
    private Throwable findFinalCause(Throwable throwable) {
        Throwable rootThrowable = null;
        String msg = null;
        
        if( throwable.getCause() == null ) {
            rootThrowable =  throwable;
        }else {
            rootThrowable = findFinalCause( throwable.getCause() );
        }
        msg = rootThrowable.getMessage();
        System.out.println( msg );
        
        return rootThrowable;
    }

}

 

 

 

 

 

 

 

正常的方法調用:  保持事物環境,不管是注解 還是 aop ,然后  不合理的 操作 ,直接 拋出異常 ,讓整個事物環境回滾。(這里只有單個方法,如果 A 調用B ,B 里面拋出異常,B 的 事物傳播 參數 指定的  是 REQUIRED(默認的),那么 就可以 正常回滾  )

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM