Hibernate Validator -对象分组验证(二)(可
紧接上回分解,没有看到第一章的童鞋可以点击此处回放上一章节内容
实体类
@Data
@AgeSalaryType
public class Student {
private Long id;
@NotBlank(message = "姓名不能为空")
private String name;
@NotNull
@Min(value = 5, message = "年龄不能低于5岁")
private int age;
@IdentifyFieldValue(enumClass = OrderType.class)
private String orderType;
@NotNull
@Digits(integer = 10, fraction = 2, message = "请保留小数点后2位")
private BigDecimal salary;
}
业务场景
新增学生信息,只需上面这些校验即可
更新学生信息,id
为必传项,需要id
属性上增加注解@NotNull
如果新建一个实体类不免显得有些鸡肋了,groups
专门解决此类问题
校验组能够让你在验证的时候选择应用哪些约束条件. 这样在某些情况下( 例如向导 ) 就可以对每一步进行校验的时候, 选取对应这步的那些约束条件进行验证了. 校验组是通过可变参数传递给
validate
,validateProperty
和validateValue
的.
如果某个约束条件属于多个组,那么各个组在校验时候的顺序是不可预知的. 如果一个约束条件没有被指明属于哪个组,那么它就会被归类到默认组(javax.validation.groups.Default).
- 新增分组接口
public interface UpdateGroup {
}
public interface AddGroup {
}
- 在实体类上为注解分配组别
@Data
@AgeSalaryType(groups = AddGroup.class)
public class Student {
@NotNull(message = "id主键不能为空", groups = UpdateGroup.class)
private Long id;
@NotBlank(message = "姓名不能为空", groups = Default.class)
private String name;
@NotNull
@Min(value = 5, message = "年龄不能低于5岁")
private int age;
@IdentifyFieldValue(enumClass = OrderType.class)
private String orderType;
@NotNull
@Digits(integer = 10, fraction = 2, message = "请保留小数点后2位")
private BigDecimal salary;
}
注:Default.class
是默认组别,无需显示声明,我只是标注出来给大家看一下例子
- 测试类
def testStudent() {
Student student = new Student();
student.setAge(20);
student.setSalary(new BigDecimal(50));
Set<ConstraintViolation<Student>> result = validator.validate(student, Default.class);
printfError(result);
Set<ConstraintViolation<Student>> result2 = validator.validate(student, UpdateGroup.class);
printfError(result2);
Set<ConstraintViolation<Student>> result3 = validator.validate(student, AddGroup.class);
printfError(result3);
expect:
true
}
- 测试结果
==================
姓名不能为空
==================
id主键不能为空
==================
当年龄大于18岁时,每月薪水不得低于100元
<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
可单独校验某个分组,这是单独分割出来校验,放在Controller
层接口上表示为:
@PostMapping(value = "all")
public String allTableType(@RequestBody @Validated(Update.class) TableType tableType) {
return JSONObject.toJSONString(tableTypeService.list());
}
关于此种在接口增加校验的方式,需要绑定BindingResult
获取错误信息,写一个完整的实例吧
- 就用上面的实体类,下面是接口
@RequestMapping("/add")
public Map<String, Object> addStudent(@Validated({Default.class, AddGroup.class}) Student student, BindingResult bindingResult) {
Map<String, Object> resultMap = new HashMap<>(10);
resultMap.put("success", true);
if (bindingResult.hasErrors()) {
resultMap.put("success", false);
StringBuilder stringBuilder = new StringBuilder();
bindingResult.getAllErrors().stream().forEach(it -> stringBuilder.append(it.getDefaultMessage()));
resultMap.put("message", stringBuilder.toString());
return resultMap;
}
···
return resultMap;
}
-
PostMan 反馈信息
postMan控制台信息
接下来再考虑一个问题,不能每个接口都增加BindingResult bindingResult
对象吧,很鸡肋的事情,可以在全局异常进行捕获输出
@ControllerAdvice
public class ValidateExceptionHandle {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public RestResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
StringBuilder stringBuilder = new StringBuilder("基础参数验证失败:");
if (ex.getBindingResult().getAllErrors().size() > 0) {
for (ObjectError allError : ex.getBindingResult().getAllErrors()) {
stringBuilder.append("[").append(allError.getDefaultMessage()).append("] ");
}
}
return RestResponse.failedMessage(stringBuilder.toString());
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public RestResponse resolveConstraintViolationException(ConstraintViolationException ex) {
StringBuilder stringBuilder = new StringBuilder("基础参数验证失败:");
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
if (!CollectionUtils.isEmpty(constraintViolations)) {
for (ConstraintViolation constraintViolation : constraintViolations) {
stringBuilder.append("[").append(constraintViolation.getMessage()).append("]");
}
}
return RestResponse.failedMessage(stringBuilder.toString());
}
接下来考虑另外一种场景,上述实体类中,如果需要校验很多组别,按顺序校验,如果前面的某个组别验证失败,就不再校验后面的组别了
@GroupSequence 定义组别之间校验的顺序
- 实体类
@Data
@AgeSalaryType(groups = AddGroup.class)
@GroupSequence({UpdateGroup.class, AddGroup.class, Student.class})
public class Student {
@NotNull(message = "id主键不能为空", groups = UpdateGroup.class)
private Long id;
@Size(min = 5, max = 10, message = "姓名长度在5-10")
private String name;
@NotNull
@Min(value = 5, message = "年龄不能低于5岁")
private int age;
@IdentifyFieldValue(enumClass = OrderType.class)
private String orderType;
@NotNull
@Digits(integer = 10, fraction = 2, message = "请保留小数点后2位")
private BigDecimal salary;
}
- 测试类
def testStudent() {
Student student = new Student();
student.setName("你哈")
student.setAge(20);
student.setSalary(new BigDecimal(50));
Set<ConstraintViolation<Student>> result = validator.validate(student);
printfError(result);
expect:
true
}
- 测试结果(UpdateGroup只校验了Id有错误就直接返回了)
id主键不能为空
将实体类上组别更换为@GroupSequence({AddGroup.class, UpdateGroup.class, Student.class})
- 测试结果(只校验了AddGroup,就返回结果)
当年龄大于18岁时,每月薪水不得低于100元
@GroupSequenceProvider 根据对象状态动态重定义默认分组
public class StudentGsProvider implements DefaultGroupSequenceProvider<Student> {
@Override
public List<Class<?>> getValidationGroups(Student student) {
List<Class<?>> defaultGroupSequence = new ArrayList<>();
defaultGroupSequence.add(Student.class);
if (student != null && student.getAge() < 18) {
defaultGroupSequence.add(AddGroup.class);
}
return defaultGroupSequence;
}
}
可以根据对象条件,将分组信息加入到默认的Default分组里面去,网上有博客用这种方式来解决对象属性之间依赖的问题,也是可行的
好了,关于Hibernate Validator
的知识点,先介绍这么多,下一节讲述如何运用Hibernate Validator
实现Excel全表校验的逻辑
Java is the best language in the world
最近北京新肺疫情应急响应级别又上升为二级,希望在帝都的朋友都多多注意,提高防范意识,下播...