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