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

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

相关推荐
Navicat中国3 小时前
Navicat 询问 AI | 如何转换 SQL 为另一种数据库类型
数据库·人工智能·sql·数据库开发·navicat
nbsaas-boot3 小时前
用 FreeMarker 动态构造 SQL 实现数据透视分析
数据库·windows·sql·freemarker·数据报表
晴子呀5 小时前
分库分表和sql的进阶用法总结
数据库·sql
Kay_Liang5 小时前
从聚合到透视:SQL 窗口函数的系统解读
大数据·数据库·sql·mysql·数据分析·窗口函数
孤狼程序员5 小时前
【Spring Cloud 微服务】1.Hystrix断路器
java·spring boot·spring·微服务
optimistic_chen8 小时前
【Java EE进阶 --- SpringBoot】初识Spring(创建SpringBoot项目)
spring boot·后端·spring·java-ee·tomcat·mvc·idea
期待のcode8 小时前
Maven
java·spring·maven·mybatis
星空下的曙光10 小时前
MySQL → SQL → DDL → 表操作 → 数据类型 知识链整理成一份系统的内容
数据库·sql·mysql
三体世界17 小时前
Mysql基本使用语句(一)
linux·开发语言·数据库·c++·sql·mysql·主键
郑州吴彦祖7721 天前
Spring MVC快速入门
spring·springmvc