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