一、Bean Validation简介

在任何时候,当你要处理一个应用程序的业务逻辑,数据校验是你必须要考虑和面对的事情。应用程序必须通过某种手段来确保输入进来的数据从语义上来讲是正确的。在通常的情况下,应用程序是分层的,不同的层由不同的开发人员来完成。很多时候同样的数据验证逻辑会出现在不同的层,这样就会导致代码冗余和一些管理的问题,比如说语义的一致性等。为了避免这样的情况发生,最好是将验证逻辑与相应的域模型进行绑定。
Bean Validation 为 JavaBean 验证定义了相应的元数据模型和 API。缺省的元数据是 Java Annotations,通过使用 XML 可以对原有的元数据信息进行覆盖和扩展。在应用程序中,通过使用 Bean Validation 或是你自己定义的 constraint,例如 @NotNull, @Max, @ZipCode, 就可以确保数据模型(JavaBean)的正确性。constraint 可以附加到字段,getter 方法,类或者接口上面。对于一些特定的需求,用户可以很容易的开发定制化的 constraint。Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。

二、简单示例

2.1 新建maven项目导入依赖

  1. <dependency>
  2. <groupId>org.hibernate.validator</groupId>
  3. <artifactId>hibernate-validator</artifactId>
  4. <version>7.0.1.Final</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.glassfish</groupId>
  8. <artifactId>jakarta.el</artifactId>
  9. <version>4.0.0</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>junit</groupId>
  13. <artifactId>junit</artifactId>
  14. <version>4.13.2</version>
  15. <scope>test</scope>
  16. </dependency>
  17. <dependency>
  18. <groupId>org.projectlombok</groupId>
  19. <artifactId>lombok</artifactId>
  20. <version>1.18.16</version>
  21. </dependency>

2.2 User实体类

@Data
public class User {

    private int id;

    @NotNull(message = "不能为空")
    @Length(min = 2,max = 10,message = "长度应该在{min}-{max}之间")
    private String name;

}

2.3 User测试类

public class UserTest {
    @Test
    public void testValidateUser() {
        User user = new User();

        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
        constraintViolations.forEach(c -> {
            System.out.println(c.getMessage());
        });
    }
}

2.4 测试结果

image.png
如果测试时给User对象set一个不符合@Length规范的数据,则出现以下结果:
image.png

三、级联校验

3.1 新建Adress实体类

@Data
public class Address {

    @NotNull(message = "街道不能为空")
    private String street;
}

3.2 修改User实体类

@Data
public class User {
    private int id;

    @NotNull(message = "不能为空")
    @Length(min = 2,max = 10,message = "长度应该在{min}-{max}之间")
    private String name;

    @NotNull(message = "地址不能为空")
    @Valid //级联校验
    private Address address;
}

当Adress属性没有加上@Valid时,无法检测到Address内部的街道属性是否为空

3.3 User测试类

public class UserTest {
    @Test
    public void testValidateUser() {
        User user = new User();
        user.setName("123");

        Address address = new Address();
        user.setAddress(address);

        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
        constraintViolations.forEach(c -> {
            System.out.println(c.getMessage());
        });
    }
}

3.4 测试结果

image.png

四、分组校验

4.1 新建分组接口

public interface UserAddGroup {
}
public interface UserUpdateGroup {
}

4.2 修改User实体类

在注释中指定groups,一个注释可以同时指定多个分组

@Data
public class User {
    private int id;

    @NotNull(message = "姓名不能为空",groups = UserAddGroup.class)
    @Length(min = 2,max = 10,message = "长度应该在(min)-(max)之间",groups = UserUpdateGroup.class)
    private String name;

    @NotNull(message = "地址不能为空")
    @Valid //级联校验
    private Address address;

}

4.3 User测试类

数据校验时,指定分组接口,如果不指定,则不检验已指定分组的注解

public class UserTest {
    @Test
    public void testValidateUser() {
        User user = new User();
        user.setName("");

        Address address = new Address();
        user.setAddress(address);

        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<User>> constraintViolations = validator.validate(user,UserUpdateGroup.class);
        constraintViolations.forEach(c -> {
            System.out.println(c.getMessage());
        });
    }
}

4.4 测试结果

image.png

如果不给User对象set名字的话,未分组时会出现姓名不能未空,分组后不会校验该@Notnull注解

public class UserTest {
    @Test
    public void testValidateUser() {
        User user = new User();
        //user.setName("");

        Address address = new Address();
        user.setAddress(address);

        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();
        Set<ConstraintViolation<User>> constraintViolations = validator.validate(user,UserUpdateGroup.class);
        constraintViolations.forEach(c -> {
            System.out.println(c.getMessage());
        });
    }
}

结果:
image.png
**

五、针对List中元素的校验

    @Size(min = 1,message = "别名至少一个")
    private List<@NotEmpty(message = "别名不能为空字符串") String> alias;

六、数据校验在Springboot中使用

6.1 导入依赖

注:如果已使用SpringCloud注册中心,则不用导入新的依赖,如果未使用,则导入以下依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
            <version>2.4.2</version>
        </dependency>

6.2 Type实体类

@Data
@TableName("ticket_type")
public class Type implements Serializable {

    private static final long serialVersionUID = 4580774108797825838L;

    @TableId(value = "id", type = IdType.AUTO)
    @NotNull(message = "id不能为空",groups = TypeUpdateGroup.class)
    private Integer id;

    //类型名
    @NotNull(message = "类型名称不能为空")
    @Length(min = 2,max = 5,message = "类型长度必须在2-5之间")
    private String name;
}

分类接口:因为update需要传入id,而add不需要,则新建分类接口来使用

public interface TypeUpdateGroup {
}

6.3 TypeController

@Validated
@Api(tags = "类型管理接口")
@RestController
@RequestMapping("/type")
public class TypeController {

    @Autowired
    private TypeService typeService;

    @ApiOperation("新增电影类型")
    @PostMapping("/add")
    public ResponseResult add(@RequestBody @Valid Type type){
        typeService.add(type);
        return new ResponseResult<>().success();
    }

    @ApiOperation("根据类型id查询类型")
    @GetMapping("/findById/{id}")
    public ResponseResult<Type> findById(@PathVariable @Min(value = 1) int id){
        System.out.println(id);
        return new ResponseResult<>();
//        return new ResponseResult<Type>().success(typeService.getById(id));
    }

    @ApiOperation("修改类型")
    @PostMapping("/update")
    public ResponseResult update(@RequestBody @Validated(TypeUpdateGroup.class) Type type){
        typeService.updateType(type);
        return new ResponseResult<>().success();
    }
}

@Validated可以用于指定分类接口,@Valid则不指定分类接口
TypeController上加@Validated,校验@Min才能生效