• 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(默认的),那么 就可以 正常回滚  )

  • 相关阅读:
    一道sql面试题
    Jedis操作redis入门
    SparkStreaming以Direct的方式对接Kafka
    SparkStreaming基于Receiver的方式对接Kafka
    spark-streaming对接kafka的两种方式
    RDD-aggregateByKey
    RDD-aggregate
    RDD五大特性
    Spark广播变量
    Spark RDD计算每天各省的top3热门广告
  • 原文地址:https://www.cnblogs.com/cxygg/p/10609173.html
Copyright © 2020-2023  润新知