利用JSR-303做数据校验
简单介绍
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
- Bean Validation 中内置的 constraint
- Hibernate 中填充一部分
如何使用
1. 给Bean添加校验注解
如下给brandId添加了一个NotNull的校验注解,表示这个值不能为空,且可以传message来自定义错误提示,如果没有则为javax中定义的默认提示。
/**
* 品牌id
*/
@NotNull(message = "修改必须指定品牌id")
@TableId
private Long brandId;
2. 开启校验功能@valid
如下是在update接口上增加了一个@Valid标注,表示开启了校验功能,校验到错误以后会有默认的响应。
/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@Valid @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
3. 获取并处理校验的结果
可以在校验的bean后紧跟一个BindingResult,就可以获得校验的结果,然后对结果中的错误进行处理。如下:
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
//如果有错误
if (result.hasErrors()) {
Map<String, String> map = new HashMap<>();
//获取校验的错误结果
result.getFieldErrors().forEach((item)->{
//获取错误的属性名
String fileld = item.getField();
//获取错误提示
String message = item.getDefaultMessage();
map.put(fileld, message);
});
//将错误信息封装返回
return R.error(400, "提交的数据不合法").put("data", map);
}else {
//无错误时处理正常逻辑
brandService.save(brand);
return R.ok();
}
}
4. 分组校验
在实际的业务开发场景中,经常有这样的一个场景,对于数据库的同一个字段,有些接口需要校验它不存在,有些接口需要校验它必须存在,典型的如新增和修改接口,对于新增接口往往不需要id(后台自动生成),对于修改接口则要求id必须存在,这种情况下就需要用到分组校验,对不同的接口使用不同的校验标注。
设置分组校验的步骤如下:
- 新建两个空的接口AddGroup和UpdateGroup,用来区分添加和更新。
//AddGrup接口
public interface AddGroup {
}
//UpdateGroup接口
public interface UpdateGroup {
}
- 在bean中的校验标注中传入指定的分组接口。
//NotNull是在更新的时候起作用
@NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class})
//Null是在新建的时候起作用
@Null(message = "新增不能指定品牌id", groups = {AddGroup.class})
//分组可以传多个
//Length的校验则会在修改和更新时都起作用
@Length(min = 0, max = 10000, message = "id必须在一定的范围内", groups = {UpdateGroup.class, AddGroup.class})
@TableId
private Long brandId;
- 在controller中的接口中增加@Validated标注
//此接口表示应用的是标记了AddGroup分组的校验标注
@RequestMapping("/save")
//@RequiresPermissions("product:brand:save")
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
//此接口表示应用的是标记了UpdateGroup分组的校验标注
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){
brandService.updateById(brand);
return R.ok();
}
对于默认没有指定分组的校验注解,在分组校验情况不生效。
5. 自定义校验
对于某些特殊的业务校验场景,系统提供的校验注解无法满足我们的需求,这个时候就需要自定义一些校验。以下将实现一个自定义校验,校验某个字段的取值是否在我们给定的特殊值范围内:
- 编写一个自定义的校验注解
@Documented
@Constraint(
validatedBy = { }
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
//此处可在配置文件中定义该检验的默认错误提示信息
String message() default "{com.atbgpi.common.vaild.NotEmpty.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
//该处我定义了合理的取值范围
int[] vals() default {};
}
- 编写一个自定义的校验器
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
private Set<Integer> set = new HashSet<>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
//这里的vals即为上面定义的校验注解里的vals传值
int[] vals = constraintAnnotation.vals();
for (int val: vals) {
set.add(val);
}
}
/**
*
* @param integer 需要校验的值
* @param constraintValidatorContext
* @return
*/
@Override
public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
//校验传入的值是否在最开始设置的vals中
return set.contains(integer);
}
}
- 关联自定义的校验器和自定义的校验注解
//在第一步定义的校验注解中的@Constraint中指定该校验注解要使用的校验器
@Constraint(
validatedBy = { ListValueConstraintValidator.class }
)
- 使用
//该处使用的是我们自定义的ListValue注解,表示需要showStatus的取值只能为0或1
@ListValue(vals={0, 1}, groups = {UpdateGroup.class, AddGroup.class})
private Integer showStatus;