Springboot模块系列:数据校验

本文介绍了Spring Boot中使用@Valid和@Validated进行数据校验的方法,包括请求体、URL参数的校验,并展示了如何通过@RestControllerAdvice和ExceptionHandler进行全局异常处理,简化错误信息返回。此外,还对比了独立数据校验的方式。
摘要由CSDN通过智能技术生成

1 入参实体

package com.company.web.dto;

import javax.validation.constraints.*;
import java.io.Serializable;

/**
 * Question and answer. 
 * @author xindaqi 
 * @since 2020-10-20
 */

public class QuestionAnswerInputDTO implements Serializable{

    @NotNull 
    @NotBlank(message = "参数为空")
    private String questions;

    @NotNull
    @NotBlank(message = "参数为空answer")
    private String answers;

    public void setQuestions(String questions) {
        this.questions = questions;
    }
    public String getQuestions() {
        return questions;
    }

    public void setAnswers(String answers) {
        this.answers = answers;
    }
    public String getAnswers() {
        return answers;
    }

    
}

2 统一返回数据

package com.company.web.vo.common;

import com.company.web.enums.common.*;

/**
 * Uniform response. 
 * @author xindaqi 
 * @since 2020-10-20
 * @param <T>
 */

public class ResponseVO<T> {

    private Integer code;

    private String msg;

    private T data;

    public void setCode(Integer code) {
        this.code = code;
    }
    public Integer getCode() {
        return code;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
    public String getMsg() {
        return msg;
    }

    public void setData(T data) {
        this.data = data;
    }
    public T getData() {
        return data;
    }

    public ResponseVO(){}

    public ResponseVO(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ResponseVO(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static ResponseVO ok() {
        return new ResponseVO(EnumsCode.SUCCESS.getCode(), EnumsCode.SUCCESS.getMsg());
    }

    public static ResponseVO ok(Object data) {
        return new ResponseVO(EnumsCode.SUCCESS.getCode(), EnumsCode.SUCCESS.getMsg(), data);
    }

    public static ResponseVO fail() {
        return new ResponseVO(EnumsCode.FAIL.getCode(), EnumsCode.FAIL.getMsg());
    }

    public static ResponseVO invalid() {
        return new ResponseVO(EnumsCode.INVALID.getCode(), EnumsCode.FAIL.getMsg());
    }

    public static ResponseVO invalid(String msg) {
        return new ResponseVO(EnumsCode.INVALID.getCode(), msg);
    }

    public static ResponseVO empty() {
        return new ResponseVO(EnumsCode.EMPTY.getCode(), EnumsCode.EMPTY.getMsg());
    }

    public static ResponseVO exception(Integer code) {
        switch (code) {
            case 4003:
                return new ResponseVO(EnumsCode.ARITHMETICEXCEPTION.getCode(), EnumsCode.ARITHMETICEXCEPTION.getMsg());
                
            case 4004:
                return new ResponseVO(EnumsCode.NULLPOINTEREXCEPTION.getCode(), EnumsCode.NULLPOINTEREXCEPTION.getMsg());
                
        }
        return new ResponseVO(EnumsCode.INVALID.getCode(), EnumsCode.INVALID.getMsg());
    }
  
}

3 数据校验

测试数据:

{
    "questions":"",
    "answers":"1234"
}

3.1 请求体数据校验

使用Valid类校验.

import javax.validation.Valid;
  • Usage
package com.company.web.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.BindException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.company.web.dto.*;
import com.company.web.service.*;
import com.company.web.vo.common.*;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.util.List;

/**
 * Transactional test.
 * @author xindaqi
 * @since 2020-10-20
 */
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController 
@RequestMapping("/api/function/test")
public class TransactionalTestController {

    static Logger logger = LoggerFactory.getLogger(TransactionalTestController.class);

    @Autowired 
    private IDataSaveService dataSaveService;

    @RequestMapping(value = "/transactional/rollback/raw", method = RequestMethod.POST)
    public ResponseVO saveDataWithRollback(@RequestBody @Valid QuestionAnswerInputDTO params) {
    // TODO logic.

    }
    
}

3.2 请求URL数据校验

  • 使用Validated类校验和NotBlank,NotNull等校验
  • 在类上使用注解@Validated
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
  • Usage
package com.company.web.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.BindException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.company.web.dto.*;
import com.company.web.service.*;
import com.company.web.vo.common.*;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.util.List;

/**
 * Transactional test.
 * @author xindaqi
 * @since 2020-10-20
 */
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController 
@RequestMapping("/api/function/test")
@Validated
public class TransactionalTestController {

    static Logger logger = LoggerFactory.getLogger(TransactionalTestController.class);

    @Autowired 
    private IDataSaveService dataSaveService;

    @RequestMapping(value = "/transactional/rollback/raw", method = RequestMethod.GET)
    public ResponseVO saveDataWithRollback(@NotBlank(message = "不能为空") @RequestParam("id") String id) {
        // TODO logic.

    }
    
}

3.3 统一数据校验

3.3.1 统一异常捕获

package com.company.web.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.MethodArgumentNotValidException;
// import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import com.company.web.vo.common.*;

import java.util.List;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;

/**
 * Uniform exception handle. 
 * @author xindaqi
 * @since 2020-10-21
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 参数绑定异常
     * @param e
     * @return
     */
    @ExceptionHandler(BindException.class) 
    public ResponseVO validationExceptionHandler(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        // fieldErrors.forEach(fieldError -> {
        //     errorMsg += fieldError.getDefaultMessage();
        // });
        
        // fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());
        // // String errorMsg = String.join(",", fieldErrors);
        // String errorMsg = fieldErrors.toString();
        String errorMsg = "bindException";
        logger.info("Bind Validataion: {}", errorMsg);

        return ResponseVO.invalid(errorMsg);
        
    }

    /**
     * 方法参数校验异常
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class) 
    public ResponseVO methodValidationExceptionHandler(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        fieldErrors.forEach(fieldError -> {
            logger.info("Invalid message: {}", fieldError.getDefaultMessage());
        });
        
        fieldErrors.stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());
        logger.info("field error: {}", fieldErrors);
        String errorMsg = fieldErrors.toString();
        logger.info("Method Validataion: {}", errorMsg);
        return ResponseVO.empty(); 
    }

    /**
     * 空指针异常
     * @param e
     * @return
     */
    @ExceptionHandler(NullPointerException.class) 
    public ResponseVO exceptionHandler(NullPointerException e) {
            String errorMsg = e.getMessage();
            logger.info("Null pointer Exception Validataion: {}", errorMsg);
            return ResponseVO.exception(4004); 
        
    }


    /**
     * 数据计算异常
     * @param e
     * @return
     */
    @ExceptionHandler(ArithmeticException.class) 
    public ResponseVO exceptionHandler(ArithmeticException e) {
            String errorMsg = e.getMessage();
            logger.info("Arithmetic Exception Validataion: {}", errorMsg);
            return ResponseVO.exception(4003); 
        
    }

    /**
     * 未知异常
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class) 
    public ResponseVO exceptionHandler(Exception e) {
            String errorMsg = e.getMessage();
            logger.info("Exception Validataion: {}", errorMsg);
            return ResponseVO.invalid(errorMsg); 
        
    }
  
}

3.3.2 数据校验

  • 统一数据校验,将捕获的异常交给全局异常处理类
  • 省去了try…catch
  • 省去了BindingResult
package com.company.web.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.BindException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.company.web.dto.*;
import com.company.web.service.*;
import com.company.web.vo.common.*;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.util.List;

/**
 * Transactional test.
 * @author xindaqi
 * @since 2020-10-20
 */
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController 
@RequestMapping("/api/function/test")
@Validated
public class TransactionalTestController {

    static Logger logger = LoggerFactory.getLogger(TransactionalTestController.class);

    @Autowired 
    private IDataSaveService dataSaveService;

    @RequestMapping(value = "/transactional/rollback/raw", method = RequestMethod.POST)
    public ResponseVO saveDataWithRollback(@RequestBody @Valid QuestionAnswerInputDTO params) {
            Boolean dataSaveFlag = dataSaveService.saveQuestionAndAnswerWithTransactionRollback(params);
            if(dataSaveFlag) {
                logger.info("成功--保存问题和答案");
                return ResponseVO.ok();
            }else {
                logger.info("失败--保存问题和答案");
                return ResponseVO.fail();
            }     

    }
 
}
  • 结果
{
    "code": 4001,
    "msg": "空数据",
    "data": null
}

3.4 独立数据校验

  • 独立数据校验使用try…catch捕获异常
  • 使用BindingResult进行数据校验
package com.company.web.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.annotation.Validated;
import org.springframework.validation.BindException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.company.web.dto.*;
import com.company.web.service.*;
import com.company.web.vo.common.*;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.util.List;

/**
 * Transactional test.
 * @author xindaqi
 * @since 2020-10-20
 */
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController 
@RequestMapping("/api/function/test")
public class TransactionalTestController {

    static Logger logger = LoggerFactory.getLogger(TransactionalTestController.class);

    @Autowired 
    private IDataSaveService dataSaveService;

    @RequestMapping(value = "/transactional/rollback/raw", method = RequestMethod.POST)
    public ResponseVO saveDataWithRollback(@RequestBody @Valid QuestionAnswerInputDTO params, BindingResult bindingResult) {
        try {
            
            if(bindingResult.hasErrors()){
                logger.info("Error:{}", bindingResult.getAllErrors());
                List<FieldError> fieldErrors = bindingResult.getFieldErrors();
                fieldErrors.forEach(fieldError ->{
                    logger.error("Error filed: {}. \n Error message: {}", fieldError.getField(), fieldError.getDefaultMessage());
                });

                return ResponseVO.empty();
            }
            Boolean dataSaveFlag = dataSaveService.saveQuestionAndAnswerWithTransactionRollback(params);
            if(dataSaveFlag) {
                logger.info("成功--保存问题和答案");
                return ResponseVO.ok();
            }else {
                logger.info("失败--保存问题和答案");
                return ResponseVO.fail();
            }

        }catch(Exception e) {
            e.printStackTrace();
            return ResponseVO.invalid();
        }

    }    
}
  • 结果
{
    "code": 4001,
    "msg": "空数据",
    "data": null
}
Error:[Field error in object 'questionAnswerInputDTO' on field 'questions': rejected value []; codes [NotBlank.questionAnswerInputDTO.questions,NotBlank.questions,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [questionAnswerInputDTO.questions,questions]; arguments []; default message [questions]]; default message [参数为空]]

4 小结

  • 数据校验:统一数据校验使用@Valid校验数据和@RestControllerAdvice增强Controller,借助ExceptionHandler捕获异常,省略try…catch
  • 数据校验:独立数据校验:使用BindingResult,独立对数据有性进行判断,需要使用try…catch
  • 数据校验:请求体校验,使用@Valid校验RequestBody
  • 数据校验:URL参数校验,使用@Validated在类上注解,同时使用NotNull等校验RequestParam

[参考文献]
[1]https://blog.csdn.net/Xin_101/article/details/109206848

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天然玩家

坚持才能做到极致

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值