Spring MVC 异常处理之 SQL 异常处理

前言:

做过业务项目开发的小伙伴都知道,开发总会避免不了各种各样的异常,为了不让每个业务繁琐的去处理异常,我们通常会去设置全局异常处理器来处理异常,本篇我们就分享如果使用全局异常处理器来处理异常。

全局异常处理器的两个注解

  • @RestControllerAdvice:用于定义全局异常处理和全局数据绑定的类,它可以被应用于带有 @Controller 或 @RestController 注解的类上,用于统一处理这些类中抛出的异常,并对返回的数据进行统一处理,本篇主要讨论全局异常处理。
  • @ExceptionHandler:注解用于统一处理控制层(Controller)往外抛的异常。

定义全局异常处理器

有了 这两个注解我们就可以定义全局异常处理器了,话不多说,直接代码演示如下:

java 复制代码
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 表单绑定到 java bean 出错时抛出 BindException 异常
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public <T> RetVo<T> processException(BindException e) {
        log.error(e.getMessage(), e);
        JSONObject msg = new JSONObject();
        e.getAllErrors().forEach(error -> {
            if (error instanceof FieldError) {
                FieldError fieldError = (FieldError) error;
                msg.put(fieldError.getField(),
                        fieldError.getDefaultMessage());
            } else {
                msg.put(error.getObjectName(),
                        error.getDefaultMessage());
            }
        });
        return RetVo.fail(RetVoEnum.PARAM_ERROR, msg.toString());
    }


    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public <T> RetVo<T> handleBindGetException(MethodArgumentNotValidException e) {
        log.error(e.getMessage(), e);
        JSONObject msg = new JSONObject();
        e.getBindingResult()
                .getFieldErrors().forEach(error -> {
                    if (error instanceof FieldError) {
                        FieldError fieldError = (FieldError) error;
                        msg.put(fieldError.getField(),
                                fieldError.getDefaultMessage());
                    } else {
                        msg.put(error.getObjectName(),
                                error.getDefaultMessage());
                    }
                });
        return RetVo.fail(RetVoEnum.PARAM_ERROR, msg.toString());
    }

    /**
     * 普通参数(非 java bean)校验出错时抛出 ConstraintViolationException 异常
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public <T> RetVo<T> processException(ConstraintViolationException e) {
        log.error(e.getMessage(), e);
        JSONObject msg = new JSONObject();
        e.getConstraintViolations().forEach(constraintViolation -> {
            String template = constraintViolation.getMessage();
            String path = constraintViolation.getPropertyPath().toString();
            msg.put(path, template);
        });
        return RetVo.fail(RetVoEnum.PARAM_ERROR, msg.toString());
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ValidationException.class)
    public <T> RetVo<T> processException(ValidationException e) {
        log.error(e.getMessage(), e);
        return RetVo.fail(RetVoEnum.PARAM_ERROR, "参数校验失败");
    }

    /**
     * NoHandlerFoundException
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoHandlerFoundException.class)
    public <T> RetVo<T> processException(NoHandlerFoundException e) {
        log.error(e.getMessage(), e);
        return RetVo.fail(RetVoEnum.RESOURCE_NOT_FOUND);
    }

    /**
     * MissingServletRequestParameterException
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public <T> RetVo<T> processException(MissingServletRequestParameterException e) {
        log.error(e.getMessage(), e);
        return RetVo.fail(RetVoEnum.PARAM_IS_NULL);
    }

    /**
     * MethodArgumentTypeMismatchException
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public <T> RetVo<T> processException(MethodArgumentTypeMismatchException e) {
        log.error(e.getMessage(), e);
        return RetVo.fail(RetVoEnum.PARAM_ERROR, "类型错误");
    }

    /**
     * ServletException
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ServletException.class)
    public <T> RetVo<T> processException(ServletException e) {
        log.error(e.getMessage(), e);
        return RetVo.fail(e.getMessage());
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(IllegalArgumentException.class)
    public <T> RetVo<T> handleIllegalArgumentException(IllegalArgumentException e) {
        log.error("非法参数异常,异常原因:{}", e.getMessage(), e);
        return RetVo.fail(e.getMessage());
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(JsonProcessingException.class)
    public <T> RetVo<T> handleJsonProcessingException(JsonProcessingException e) {
        log.error("Json转换异常,异常原因:{}", e.getMessage(), e);
        return RetVo.fail(e.getMessage());
    }

    /**
     * HttpMessageNotReadableException
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public <T> RetVo<T> processException(HttpMessageNotReadableException e) {
        log.error(e.getMessage(), e);
        String errorMessage = "请求体不可为空";
        Throwable cause = e.getCause();
        if (cause != null) {
            errorMessage = convertMessage(cause);
        }
        return RetVo.fail(errorMessage);
    }

    /**
     * TypeMismatchException
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(TypeMismatchException.class)
    public <T> RetVo<T> processException(TypeMismatchException e) {
        log.error(e.getMessage(), e);
        return RetVo.fail(e.getMessage());
    }


    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BizException.class)
    public <T> RetVo<T> handleBizException(BizException e) {
        log.error("业务异常,异常原因:{}", e.getMessage(), e);
        if (e.getResultCode() != null) {
            return RetVo.fail(e.getResultCode());
        }
        return RetVo.fail(e.getMessage());
    }

    @ExceptionHandler(ZtenException.class)
    public <T> RetVo<T> handleZtenException(ZtenException e) {
        log.error("业务异常,异常原因:{},堆栈信息:{}", e.getMessage(), e);
        return RetVo.fail(String.valueOf(e.getCode()), e.getMessage());
    }

    @ExceptionHandler(DataAccessException.class)
    public <T> RetVo<T> handleSQLSyntaxErrorException(DataAccessException e) {
        log.error("sql 错误,请确认sql是否正确");
        e.printStackTrace();
        return RetVo.fail("系统异常,请联系系统管理员处理");
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(Exception.class)
    public <T> RetVo<T> handleException(Exception e) {
        log.error("程序异常,异常详情:", e);
        return RetVo.fail(e.getLocalizedMessage());
    }

    /**
     * 传参类型错误时,用于消息转换
     *
     * @param throwable 异常
     * @return 错误信息
     */
    private String convertMessage(Throwable throwable) {
        String error = throwable.toString();
        String regulation = "\\[\"(.*?)\"]+";
        Pattern pattern = Pattern.compile(regulation);
        Matcher matcher = pattern.matcher(error);
        String group = "";
        if (matcher.find()) {
            String matchString = matcher.group();
            matchString = matchString
                    .replace("[", "")
                    .replace("]", "");
            matchString = matchString.replaceAll("\\\"", "") + "字段类型错误";
            group += matchString;
        }
        return group;
    }
}

这个异常管理器对一般异常来说都好使,直到有一天业务反馈系统报错,直接打印了 SQL 语句,才意识到这个异常处理器的有巨大的问题,那针对 SQL 异常我们该如何处理呢?

全局处理 SQL 异常

SQL 语句直接暴露给用户,不仅仅体验感不好,还很容易就暴露数据库得表结构,为了避免 SQL 异常被用户感知,想到了加 SQL 异常拦截,想到了 SQLException 是所有 SQL 异常的子类,直接拦截 SQLException 即可,代码如下:

java 复制代码
@ExceptionHandler(SQLException.class)
public <T> RetVo<T> handleSQLSyntaxErrorException(DataAccessException e) {
	log.error("sql 错误,请确认sql是否正确");
	e.printStackTrace();
	return RetVo.fail("系统异常,请联系系统管理员处理");
}

没想到加了上述代码后,任然还是抛出了 SQL 语句,异常拦截无效,究竟是怎么回事呢?

是因为 @ExceptionHandler 注解只认我们注入的类名称,SQLException 类并没有注入,因此不生效,那怎么办?

Spring 为我们提供了 DataAccessException 类来处理 SQL 异常,我们只需要将 @ExceptionHandler 值换成 DataAccessException.class 即可,代码如下:

java 复制代码
@ExceptionHandler(DataAccessException.class)
public <T> RetVo<T> handleSQLSyntaxErrorException(DataAccessException e) {
	log.error("sql 错误,请确认sql是否正确");
	e.printStackTrace();
	return RetVo.fail("系统异常,请联系系统管理员处理");
}

测试结果:

powershell 复制代码
{
    "code": "30002",
    "message": "系统异常,请联系系统管理员处理",
    "data": null
}

一个全局异常处理器看似简单,但是可能也存在漏洞,比如上面介绍的 SQL 异常的处理,SQL 异常的处理有一定的特殊性,因此分享给大家,希望帮助到有需要的小伙伴。

欢迎提出建议及对错误的地方指出纠正。

相关推荐
zjw_rp11 分钟前
Spring-AOP
java·后端·spring·spring-aop
cmdch20173 小时前
Mybatis加密解密查询操作(sql前),where要传入加密后的字段时遇到的问题
数据库·sql·mybatis
程序猿小柒3 小时前
【Spark】Spark SQL执行计划-精简版
大数据·sql·spark
王ASC3 小时前
SpringMVC的URL组成,以及URI中对/斜杠的处理,解决IllegalStateException: Ambiguous mapping
java·mvc·springboot·web
撒呼呼3 小时前
# 起步专用 - 哔哩哔哩全模块超还原设计!(内含接口文档、数据库设计)
数据库·spring boot·spring·mvc·springboot
天使day4 小时前
SpringMVC
java·spring·java-ee
听见~5 小时前
SQL优化
数据库·sql
壹佰大多6 小时前
【spring-cloud-gateway总结】
java·spring·gateway
CodeChampion6 小时前
60.基于SSM的个人网站的设计与实现(项目 + 论文)
java·vue.js·mysql·spring·elementui·node.js·mybatis
秋意钟6 小时前
Spring框架处理时间类型格式
java·后端·spring