数据校验-Hibernate Validator提升后端校验体验

 

目录

Hibernate Validator的使用

依赖

bean约束声明和验证,Validator

方法约束声明和验证,ExecutableValidator

约束注解

空与非空检查

Boolean值检查

日期检查

数值检查

其他

hibernate-validator扩展约束(部分)

自定义约束注解

分组约束

在Spring中使用Hibernate Validator

配置Validator

请求参数bean验证

全局异常处理器

返回消息

方法参数验证

配置

使用

分组

常见问题

1. HV000030: No validator could be found for constraint 'javax.validation.constraints.NotBlank' validating type 'java.util.List'. Check configuration for 'list[0].XXX'

解决办法

空与非空检查

2. @validated 验证 List 参数无效

解决办法


数据校验是在平时的编码过程中常做的工作,在系统的各个层可能都要去实现一些校验逻辑,再去做业务处理。这些繁琐的校验与我们的业务代码在一块就会显得臃肿。而且这些校验通常是业务无关的。也是在工作中使用到Hibernate Validator,但却发现有人没有使用好它(竟然还能看到一些if else的校验代码...),所以在这里决定整理下关于Hibernate Validator的使用

Bean Validation 2.0(JSR 380)定义了用于实体和方法验证的元数据模型和API,Hibernate Validator是目前最好的实现,这篇主要是说Hibernate Validator的使用

Hibernate Validator的使用

依赖

如果是Spring Boot项目,那么spring-boot-starter-web中就已经依赖hibernate-validator


    org.springframework.boot
    spring-boot-starter-web

如果是Spring Mvc,那可以直接添加hibernate-validator依赖


    org.hibernate.validator
    hibernate-validator
    6.0.17.Final

bean约束声明和验证,Validator

先给我们的Java对象添加约束注解

@Data
@AllArgsConstructor
public class User {

    private String id;

    @NotBlank
    @Size(max = 20)
    private String name;

    @NotNull
    @Pattern(regexp = "[A-Z][a-z][0-9]")
    private String password;
    
    @NotNull
    private Integer age;

    @Max(10)
    @Min(1)
    private Integer level;
}

验证实体实例需要先获取Validator实例

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();

Validator接口有三个方法,可用于验证整个实体仅验证实体的单个属性

  • Validator#validate() 验证所有bean的所有约束
  • Validator#validateProperty() 验证单个属性
  • Validator#validateValue() 检查给定类的单个属性是否可以成功验证
public class UserTest {

    private static Validator validator;

    @BeforeAll
    public static void setUpValidator() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    @Test
    public void validatorTest() {
        User user = new User(null, "", "!@#$", null, 11);

        // 验证所有bean的所有约束
        Set> constraintViolations = validator.validate(user);
        // 验证单个属性
        Set> constraintViolations2 = validator.validateProperty(user, "name");
        // 检查给定类的单个属性是否可以成功验证
        Set> constraintViolations3 = validator.validateValue(User.class, "password", "sa!");

        constraintViolations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
        constraintViolations2.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
        constraintViolations3.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
    }
}

测试结果

不能为空
最大不能超过10
需要匹配正则表达式"[A-Z][a-z][0-9]"
不能为null
不能为空
需要匹配正则表达式"[A-Z][a-z][0-9]"

方法约束声明和验证,ExecutableValidator

从Bean Validation 1.1开始,约束不仅可以应用于JavaBean及其属性,而且可以应用于任何Java类型的方法和构造函数的参数和返回值,这里简单看一个例子

public class RentalStation {

    public RentalStation(@NotNull String name) {
        //...
    }

    public void rentCar(@NotNull @Future LocalDate startDate, @Min(1) int durationInDays) {
        //...
    }

    @NotNull
    @Size(min = 1)
    public List<@NotNull String> getCustomers() {
        //...
        return null;
    }
}

ExecutableValidator接口可以完成方法约束的验证

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
executableValidator = factory.getValidator().forExecutables();

ExecutableValidator界面共有四种方法:

  • validateParameters()validateReturnValue()用于方法验证
  • validateConstructorParameters()validateConstructorReturnValue()用于构造函数验证
public class RentalStationTest {

    private static ExecutableValidator executableValidator;

    @BeforeAll
    public static void setUpValidator() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        executableValidator = factory.getValidator().forExecutables();
    }

    @Test
    public void validatorTest() throws NoSuchMethodException {
        RentalStation rentalStation = new RentalStation("z");

        Method method = RentalStation.class.getMethod("rentCar", LocalDate.class, int.class);
        Object[] parameterValues = {LocalDate.now().minusDays(1), 0};
        Set> violations = executableValidator.validateParameters(
                rentalStation, method, parameterValues);

        violations.forEach(violation -> System.out.println(violation.getMessage()));
    }
}

测试结果

需要是一个将来的时间
最小不能小于1

约束注解

validator-api-2.0的约束注解有22个,具体我们看下面表格

空与非空检查

注解 支持Java类型 说明
@Null Object 为null
@NotNull Object 不为null
@NotBlank CharSequence 不为null,且必须有一个非空格字符
@NotEmpty CharSequence、Collection、Map、Array 不为null,且不为空(length/size>0)

Boolean值检查

注解 支持Java类型 说明 备注
@AssertTrue boolean、Boolean 为true 为null有效
@AssertFalse boolean、Boolean 为false 为null有效

日期检查

注解 支持Java类型 说明 备注
@Future Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate 验证日期为当前时间之后 为null有效
@FutureOrPresent Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate 验证日期为当前时间或之后 为null有效
@Past Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate 验证日期为当前时间之前 为null有效
@PastOrPresent Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate 验证日期为当前时间或之前 为null有效

数值检查

注解 支持Java类型 说明 备注
@Max BigDecimal、BigInteger,byte、short、int、long以及包装类 小于或等于 为null有效
@Min BigDecimal、BigInteger,byte、short、int、long以及包装类 大于或等于 为null有效
@DecimalMax BigDecimal、BigInteger、CharSequence,byte、short、int、long以及包装类 小于或等于 为null有效
@DecimalMin BigDecimal、BigInteger、CharSequence,byte、short、int、long以及包装类 大于或等于 为null有效
@Negative BigDecimal、BigInteger,byte、short、int、long、float、double以及包装类 负数 为null有效,0无效
@NegativeOrZero BigDecimal、BigInteger,byte、short、int、long、float、double以及包装类 负数或零 为null有效
@Positive BigDecimal、BigInteger,byte、short、int、long、float、double以及包装类 正数 为null有效,0无效
@PositiveOrZero BigDecimal、BigInteger,byte、short、int、long、float、double以及包装类 正数或零 为null有效
@Digits(integer = 3, fraction = 2) BigDecimal、BigInteger、CharSequence,byte、short、int、long以及包装类 整数位数和小数位数上限 为null有效

其他

注解 支持Java类型 说明 备注
@Pattern CharSequence 匹配指定的正则表达式 为null有效
@Email CharSequence 邮箱地址 为null有效,默认正则 '.*'
@Size CharSequence、Collection、Map、Array 大小范围(length/size>0) 为null有效

hibernate-validator扩展约束(部分)

注解 支持Java类型 说明
@Length String 字符串长度范围
@Range 数值类型和String 指定范围
@URL   URL地址验证

自定义约束注解

除了以上提供的约束注解(大部分情况都是能够满足的),我们还可以根据自己的需求自定义自己的约束注解

定义自定义约束,有三个步骤

  • 创建约束注解
  • 实现一个验证器
  • 定义默认的错误信息

那么下面就直接来定义一个简单的验证手机号码的注解

@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = {MobileValidator.class})
@Retention(RUNTIME)
@Repeatable(Mobile.List.class)
public @interface Mobile {

    /**
     * 错误提示信息,可以写死,也可以填写国际化的key
     */
    String message() default "手机号码不正确";

    Class[] groups() default {};
    
    Class[] payload() default {};

    String regexp() default "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";

    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        Mobile[] value();
    }
}

关于注解的配置这里不说了,自定义约束需要下面3个属性

  • message 错误提示信息,可以写死,也可以填写国际化的key
  • groups 分组信息,允许指定此约束所属的验证组(下面会说到分组约束)
  • payload 有效负载,可以通过payload来标记一些需要特殊处理的操作
  • @Repeatable注解List定义可以让该注解在同一个位置重复多次,通常是不同的配置(比如不同的分组和消息)
  • @Constraint(validatedBy = {MobileValidator.class})该注解是指明我们的自定义约束的验证器,那下面就看一下验证器的写法,需要实现javax.validation.ConstraintValidator接口
public class MobileValidator implements ConstraintValidator {

    /**
     * 手机验证规则
     */
    private Pattern pattern;

    @Override
    public void initialize(Mobile mobile) {
        pattern = Pattern.compile(mobile.regexp());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }

        return pattern.matcher(value).matches();
    }
}

ConstraintValidator接口定义了在实现中设置的两个类型参数。

  1. 第一个指定要验证的注解类(如Mobile),
  2. 第二个指定验证器可以处理的元素类型(如String);initialize()方法可以访问约束注解的属性值;isValid()方法用于验证,返回true表示验证通过

Bean验证规范建议将空值视为有效。如果null不是元素的有效值,则应使用@NotNull 显式注释

到这里我们自定义的约束就写好了,可以用个例子来测试一下

public class MobileTest {

    public void setMobile(@Mobile String mobile){
        // to do
    }

    private static ExecutableValidator executableValidator;

    @BeforeAll
    public static void setUpValidator() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        executableValidator = factory.getValidator().forExecutables();
    }

    @Test
    public void manufacturerIsNull() throws NoSuchMethodException {
        MobileTest mobileTest = new MobileTest();

        Method method = MobileTest.class.getMethod("setMobile", String.class);
        Object[] parameterValues = {"1111111"};
        Set> violations = executableValidator.validateParameters(
                mobileTest, method, parameterValues);

        violations.forEach(violation -> System.out.println(violation.getMessage()));
    }
}
手机号码不正确

分组约束

在上面的自定义约束中,有个groups属性是用来指定验证约束的分组,我们在为属性加上注解的时候,如果没有配置分组信息,那么默认会采用默认分组 javax.validation.groups.Default

分组是用接口定义的,用做标识,这里创建两个标识AddGroupUpdateGroup,分别标识新增和修改

新增用户信息的时候,不需要验证id(因为系统生成);修改的时候需要验证id,这时候可用用户到validator的分组验证功能。

public interface AddGroup {
}

public interface UpdateGroup {
}

然后对我们的User对象的id属性做分组标识

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    @Null(groups = AddGroup.class)
    @NotBlank(groups = UpdateGroup.class)
    private String id;
    
    // ... 省略了其他属性
}    

我们看下如何使用

@Test
public void validatorGroupTest() {
	User user = new User();

    // 检查给定类的单个属性是否可以成功验证
    Set> constraintViolations = validator.validateValue(User.class, "id", "", UpdateGroup.class);
    Set> constraintViolations2 = validator.validateValue(User.class, "id", "");
    Set> constraintViolations3 = validator.validateValue(User.class, "id", "", AddGroup.class);

	constraintViolations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
	constraintViolations2.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
	constraintViolations3.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
}

上面的测试只有加了UpdateGroug分组才会验证,返回错误信息,而下面的constraintViolations2并不会去验证,因为默认会采用Default分组。如果想要不标记分组的时候,也会去验证Default分组,可以去继承默认分组

public interface AddGroup extends Default {
}

分组顺序校验时,按指定的分组先后顺序进行验证,前面的验证不通过,后面的分组就不行验证。

在Spring中使用Hibernate Validator

上面介绍了Validator的一些使用,还有注解的介绍,那么在Spring中我们怎么去使用Hibernate Validator做验证呢?或者说再Web项目中怎么使用Hibernate Validator?

spring-boot-starter-web中是添加了hibernate-validator依赖的,说明Spring Boot本身也是使用到了Hibernate Validator验证框架的

配置Validator

@Configuration
public class ValidatorConfig {

    /**
     * 配置验证器
     *
     * @return validator
     */
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 快速失败模式
                .failFast(true)
                // .addProperty( "hibernate.validator.fail_fast", "true" )
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }
}

可以通过方法 failFast(true)addProperty("hibernate.validator.fail_fast", "true")设置为快速失败模式,快速失败模式在校验过程中,当遇到第一个不满足条件的参数时就立即返回,不再继续后面参数的校验。否则会一次性校验所有参数,并返回所有不符合要求的错误信息

如果是Spring MVC的话,需要xml配置可参考下面的配置





    
    
    



    
    
    
    

请求参数bean验证

接口上的Bean验证,需要在参数前加上@Valid或Spring的 @Validated注解,这两种注释都会导致应用标准Bean验证。

如果验证不通过会抛出BindException异常,并变成400(BAD_REQUEST)响应;或者可以通过ErrorsBindingResult参数在控制器内本地处理验证错误。另外,如果参数前有@RequestBody注解,验证错误会抛出MethodArgumentNotValidException异常。

@RestController
public class UserController {

    @PostMapping("/user")
    public R handle(@Valid @RequestBody User user, BindingResult result) {
        // 在控制器内本地处理验证错误
        if (result.hasErrors()) {
            result.getAllErrors().forEach(s -> System.out.println(s.getDefaultMessage()));
             return R.fail(result.getAllErrors().get(0).getDefaultMessage());
        }
        // ...
        return R.success();
    }

    @PostMapping("/user2")
    public R handle2(@Valid User user, BindingResult result) {
        // 在控制器内本地处理验证错误
        if (result.hasErrors()) {
            result.getAllErrors().forEach(s -> System.out.println(s.getDefaultMessage()));
             return R.fail(result.getAllErrors().get(0).getDefaultMessage());
        }
        // ...
        return R.success();
    }

    /**
     * 验证不通过抛出 `MethodArgumentNotValidException`
     */
    @PostMapping("/user3")
    public R handle3(@Valid @RequestBody User user) {
        // ...
        return R.success();
    }

    /**
     * 验证不通过抛出 `BindException`
     */
    @PostMapping("/user4")
    public R handle4(@Valid User user) {
        // ...
        return R.success();
    }
}

配合Spring的BindingResult参数,我们是可以在控制器中去处理验证错误,不过通常也是把验证错误的消息转成我们自己的返回格式,那么在每个方法中都去做这样的验证错误处理,显然是没有必要的。

我们可以利用验证不通过的异常来做统一的错误处理

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * hibernate validator 数据绑定验证异常拦截
     *
     * @param e 绑定验证异常
     * @return 错误返回消息
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public R validateErrorHandler(BindException e) {
        ObjectError error = e.getAllErrors().get(0);
        log.info("数据验证异常:{}", error.getDefaultMessage());
        return R.fail(error.getDefaultMessage());
    }

    /**
     * hibernate validator 数据绑定验证异常拦截
     *
     * @param e 绑定验证异常
     * @return 错误返回消息
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public R validateErrorHandler(MethodArgumentNotValidException e) {
        ObjectError error = e.getBindingResult().getAllErrors().get(0);
        log.info("数据验证异常:{}", error.getDefaultMessage());
        return R.fail(error.getDefaultMessage());
    }
}    

或者

全局异常处理器

package com.devicemag.common.security.handler;

import com.devicemag.common.core.exception.BaseException;
import com.devicemag.common.core.exception.CustomException;
import com.devicemag.common.core.exception.DemoModeException;
import com.devicemag.common.core.exception.PreAuthorizeException;
import com.devicemag.common.core.utils.StringUtils;
import com.devicemag.common.core.web.domain.AjaxResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理器
 *
 * @author wangyanfei
 */
@RestControllerAdvice
public class GlobalExceptionHandler
{
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 基础异常
     */
    @ExceptionHandler(BaseException.class)
    public AjaxResult baseException(BaseException e)
    {
        return AjaxResult.error(e.getDefaultMessage());
    }

    /**
     * 业务异常
     */
    @ExceptionHandler(CustomException.class)
    public AjaxResult businessException(CustomException e)
    {
        if (StringUtils.isNull(e.getCode()))
        {
            return AjaxResult.error(e.getMessage());
        }
        return AjaxResult.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public AjaxResult handleException(Exception e)
    {
        log.error(e.getMessage(), e);
        return AjaxResult.error(e.getMessage());
    }

    /**
     * 自定义验证异常
     */
    @ExceptionHandler(BindException.class)
    public AjaxResult validatedBindException(BindException e)
    {
        log.error(e.getMessage(), e);
        String message = e.getAllErrors().get(0).getDefaultMessage();
        return AjaxResult.error(message);
    }

    /**
     * 自定义验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object validExceptionHandler(MethodArgumentNotValidException e)
    {
        log.error(e.getMessage(), e);
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        return AjaxResult.error(message);
    }
    
  
}

返回消息

package com.devicemag.common.core.web.domain;

import com.devicemag.common.core.constant.HttpStatus;
import com.devicemag.common.core.utils.StringUtils;

import java.util.HashMap;

/**
 * 操作消息提醒
 *
 * @author wangyanfei
 */
public class AjaxResult extends HashMap
{
    private static final long serialVersionUID = 1L;

    /** 状态码 */
    public static final String CODE_TAG = "code";

    /** 返回内容 */
    public static final String MSG_TAG = "msg";

    /** 数据对象 */
    public static final String DATA_TAG = "data";

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult()
    {
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     * 
     * @param code 状态码
     * @param msg 返回内容
     */
    public AjaxResult(int code, String msg)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     * 
     * @param code 状态码
     * @param msg 返回内容
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, Object data)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data))
        {
            super.put(DATA_TAG, data);
        }
    }
    
    /**
     * 方便链式调用
     *
     * @param key
     * @param value
     * @return
     */
    @Override
    public AjaxResult put(String key, Object value)
    {
        super.put(key, value);
        return this;
    }

    /**
     * 返回成功消息
     * 
     * @return 成功消息
     */
    public static AjaxResult success()
    {
        return AjaxResult.success("操作成功");
    }

    /**
     * 返回成功数据
     * 
     * @return 成功消息
     */
    public static AjaxResult success(Object data)
    {
        return AjaxResult.success("操作成功", data);
    }

    /**
     * 返回成功消息
     * 
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult success(String msg)
    {
        return AjaxResult.success(msg, null);
    }

    /**
     * 返回成功消息
     * 
     * @param msg 返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
    }

    /**
     * 返回错误消息
     * 
     * @return
     */
    public static AjaxResult error()
    {
        return AjaxResult.error("操作失败");
    }

    /**
     * 返回错误消息
     * 
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(String msg)
    {
        return AjaxResult.error(msg, null);
    }

    /**
     * 返回错误消息
     * 
     * @param msg 返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult error(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.ERROR, msg, data);
    }

    /**
     * 返回错误消息
     * 
     * @param code 状态码
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(int code, String msg)
    {
        return new AjaxResult(code, msg, null);
    }
}

方法参数验证

配置

Hibernate Validator是可以在方法级验证参数的,Spring中当然也是有实现的。

我们在Validator的配置中,添加MethodValidationPostProcessorBean,在上面的ValidatorConfig.java中添加一下配置

/**
 * 设置方法参数验证器
 */
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
    MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
    // 设置validator模式为快速失败返回
    postProcessor.setValidator(validator());
    return postProcessor;
}

如果是Spring Mvc,那么要在spring-mvc.xml中声明bean信息,不然在Controller里面是无效的



    

使用

配置了上面的MethodValidationPostProcessor,我们就可以在方法参数或返回值使用约束注解了,要注意的是,在要使用参数验证的类上一定要加上@Validated注解,否则无效

/**
 * 一定要加上 `@Validated` 注解
 */
@Validated
@RestController
public class UserController {

    @GetMapping("/user")
    public R handle(@Mobile String mobile) {
        // ...
        return R.success();
    }
}

如果验证不通过,会抛出ConstraintViolationException异常,同样的,我们可以在全局的异常处理器里面处理验证错误,在GlobalExceptionHandler中添加一下代码

/**
 * spring validator 方法参数验证异常拦截
 *
 * @param e 绑定验证异常
 * @return 错误返回消息
 */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public R defaultErrorHandler(ConstraintViolationException e) {
    Set> violations = e.getConstraintViolations();
    ConstraintViolation violation = violations.iterator().next();
    log.info("数据验证异常:{}", violation.getMessage());
    return R.fail(violation.getMessage());
}

分组

Spring的@Validate注解是可以支持分组验证的

@PostMapping("/user")
public R handle(@Validated(AddGroup.class) @RequestBody User user) {
    // ...
    return R.success();
}

常见问题

1. HV000030: No validator could be found for constraint 'javax.validation.constraints.NotBlank' validating type 'java.util.List'. Check configuration for 'list[0].XXX'

解决办法

@NotBlank是不能用于list,只能用于字符string类型,要验证list非空,可以使用@NotEmpty

具体参考下面,

空与非空检查

注解 支持Java类型 说明
@Null Object 为null
@NotNull Object 不为null
@NotBlank CharSequence 不为null,且必须有一个非空格字符
@NotEmpty CharSequence、Collection、Map、Array 不为null,且不为空(length/size>0)

2. @validated 验证 List 参数无效

解决办法

把List 换成 ValidableList 

@ApiOperation("数据上报-基本信息")
@PostMapping("/report")
public AjaxResult send(@Validated @RequestBody List reportSimulatorInfoList) {
	
}

改成如下
 
 
@ApiOperation("数据上报-基本信息")
@PostMapping("/report")
public AjaxResult send(@Validated @RequestBody ValidList reportSimulatorInfoList) {
	
}

自定义ValidableList类用来验证

package com.devicemag.core.BO;

import javax.validation.Valid;
import java.util.*;

/**
 * @Title: 参数校验工具类, 用于校验List 类型的请求参数
 * @ClassName: com.devicemag.core.BO.ValidList.java
 * @Description:
 *
 * @Copyright 2020-2021 - Powered By 研发中心
 * @author: 王延飞
 * @date: 2020/12/25 20:23
 * @version V1.0
 */
public class ValidList implements List {

    @Valid
    private List list = new ArrayList<>();

    @Override
    public int size() {
        return list.size();
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return list.contains(o);
    }

    @Override
    public Iterator iterator() {
        return list.iterator();
    }

    @Override
    public Object[] toArray() {
        return list.toArray();
    }

    @Override
    public  T[] toArray(T[] a) {
        return list.toArray(a);
    }

    @Override
    public boolean add(E e) {
        return list.add(e);
    }

    @Override
    public boolean remove(Object o) {
        return list.remove(o);
    }

    @Override
    public boolean containsAll(Collection c) {
        return list.containsAll(c);
    }

    @Override
    public boolean addAll(Collection c) {
        return list.addAll(c);
    }

    @Override
    public boolean addAll(int index, Collection c) {
        return list.addAll(index, c);
    }

    @Override
    public boolean removeAll(Collection c) {
        return list.removeAll(c);
    }

    @Override
    public boolean retainAll(Collection c) {
        return list.retainAll(c);
    }

    @Override
    public void clear() {
        list.clear();
    }

    @Override
    public E get(int index) {
        return list.get(index);
    }

    @Override
    public E set(int index, E element) {
        return list.set(index, element);
    }

    @Override
    public void add(int index, E element) {
        list.add(index, element);
    }

    @Override
    public E remove(int index) {
        return list.remove(index);
    }

    @Override
    public int indexOf(Object o) {
        return list.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        return list.lastIndexOf(o);
    }

    @Override
    public ListIterator listIterator() {
        return list.listIterator();
    }

    @Override
    public ListIterator listIterator(int index) {
        return list.listIterator(index);
    }

    @Override
    public List subList(int fromIndex, int toIndex) {
        return list.subList(fromIndex, toIndex);
    }

    public List getList() {
        return list;
    }

    public void setList(List list) {
        this.list = list;
    }

    @Override
    public String toString() {
        return "ValidList{" +
                "list=" + list +
                '}';
    }
}

参考来源:

 

https://stackoverflow.com/questions/28150405/validation-of-a-list-of-objects-in-spring

你可能感兴趣的:(琦彦の百宝箱,数据校验,Hibernate,Validator,Validated)