1、JSR303是什么?
JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指 :向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。
任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303规范中所有内置constraint 的实现,除此之外还有一些附加的 constraint
2、JSR303如何使用
1) 给bean添加校验注解 :javax.validation.constraints
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private Long brandId;
@NotBlank(message = "品牌名称不能为空")
private String name;
@URL(message = "logo必须是一个合法的url")
private String logo;
private String descript;
private Integer showStatus;
//自定义校验规则
@Pattern(regexp = "/^[a-zA-Z]$/",message = "检索首字母必须是一个字母")
private String firstLetter;
private Integer sort;
}2) 在congtroller总开启校验 @Valid
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}校验错误后会有默认的响应
3) 如果不想要默认的响应,希望自己自定义校验异常返回的结果
给校验后的bean后面紧跟一个BindingResult就可以获取到校验的结果
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
if(result.hasErrors()){
Map<String,String> map = new HashMap<>();
//1、获取校验的错误结果
result.getFieldErrors().forEach((item) -> {
//获取到错误提示
String message = item.getDefaultMessage();
//获取错误属性的名字
String field = item.getField();
map.put(field,message);
});
return R.error(400,"提交的数据不合法").put("data", map);
}
brandService.save(brand);
return R.ok();
}4) 如果每个controller都需要写这种封装数据校验异常的代码,会造成代码冗余
此时需要进行统一异常处理,并将controller方法中的BindingResult去掉,任由其抛出异常
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}添加controller统一异常处理器
@Slf4j
@RestControllerAdvice
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
log.error("数据校验异常{},异常类型{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
HashMap<String, String> map = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError) -> {
map.put(fieldError.getField(),fieldError.getDefaultMessage());
} );
return R.error(400,"数据校验错误").put("data",map);
}
}发送postman请求可以看到,返回的格式是我们自己封装的response格式:
3、分组校验
给校验注解标注什么时候需要校验
试想一下,新增和更新时,对实体的不同字段的校验是不同的,例如在新增时主键必须为空,但是修改时,主键必须不为空,此时就必须要用到分组校验
1) 声明分组,实际上是接口
2) 实体属性校验上添加分组
注意:默认没有指定分组的校验注解,在分组校验的情况下不生效
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull(message = "修改必须携带品牌id",groups = {UpdateGroup.class})
@Null(message = "新增不能携带品牌id",groups = {AddGroup.class})
@TableId
private Long brandId;
@NotBlank(message = "品牌名称不能为空",groups = {AddGroup.class,UpdateGroup.class})
private String name;
@NotBlank(message = "logo地址不能为空",groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的url",groups = {AddGroup.class,UpdateGroup.class})
private String logo;
private String descript;
private Integer showStatus;
@Pattern(regexp = "/^[a-zA-Z]$/",message = "检索首字母必须是一个字母")
private String firstLetter;
private Integer sort;
}3) controller中添加 @Validated({AddGroup.class})
指定需要校验的分组
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}4) 测试
4、自定义校验注解
1) 编写一个自定义的校验注解
找到 @NotNull注解,将其上的元注解copy下来
@Documented
@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.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] vals() default {};
}2) 编写一个自定义的校验器
public class ListValueContraintValidator implements ConstraintValidator<ListValue, Integer> {
private Set<Integer> set = new HashSet<>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for(int val : vals){
set.add(val);
}
}
//判断校验是否成功 value=> controller接收的值 == 等待被校验的值 @Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
return set.contains(value);
}
}3) 将校验器和注解绑定
@Documented
//表明本校验注解 使用ListValueContraintValidator校验器
@Constraint(validatedBy = {ListValueContraintValidator.class})
@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.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] vals() default {};
}4) 在实体类上使用该校验注解
@ListValue(vals = {0,1},groups = AddGroup.class) 的意思时,新增时校验showStatus,且showStatus必须是 0 / 1
@ListValue(vals = {0,1},groups = AddGroup.class) //当新增时携带了showStatus字段
private Integer showStatus;5) 测试





