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 异常的处理有一定的特殊性,因此分享给大家,希望帮助到有需要的小伙伴。

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

相关推荐
java亮小白19971 小时前
Spring循环依赖如何解决的?
java·后端·spring
武子康2 小时前
大数据-230 离线数仓 - ODS层的构建 Hive处理 UDF 与 SerDe 处理 与 当前总结
java·大数据·数据仓库·hive·hadoop·sql·hdfs
苏-言2 小时前
Spring IOC实战指南:从零到一的构建过程
java·数据库·spring
草莓base2 小时前
【手写一个spring】spring源码的简单实现--容器启动
java·后端·spring
冰帝海岸9 小时前
01-spring security认证笔记
java·笔记·spring
没书读了9 小时前
ssm框架-spring-spring声明式事务
java·数据库·spring
武子康10 小时前
Java-06 深入浅出 MyBatis - 一对一模型 SqlMapConfig 与 Mapper 详细讲解测试
java·开发语言·数据仓库·sql·mybatis·springboot·springcloud
爱上口袋的天空11 小时前
09 - Clickhouse的SQL操作
数据库·sql·clickhouse
代码小鑫12 小时前
A043-基于Spring Boot的秒杀系统设计与实现
java·开发语言·数据库·spring boot·后端·spring·毕业设计
真心喜欢你吖13 小时前
SpringBoot与MongoDB深度整合及应用案例
java·spring boot·后端·mongodb·spring