JSR303校验(分组校验,自定义校验)
JSR303的使用:
使用步骤:
1.添加依赖
2.在entity类的属性上添加注解
3.开启校验功能:在controller类的方法的参数上加上@Valid属性
4.校验失败的处理。
@Data
public class EmployeeReq {
@NotEmpty(message = "请选择员工类型")
private String employeesType;
@NotEmpty(message = "请输入员工姓名")
private String name;
@NotEmpty(message = "请输入IC卡号")
private String icNo;
@NotEmpty(message = "请输入联系电话")
private String mobilePhone;
}
controller层 方法参数中加@Valid注解
@RestController
@RequestMapping("/web/employee")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@PostMapping("/addEmployee")
public void createEmployee(@Valid @RequestBody EmployeeReq employeeReq, BindingResult bindingResult) throws Exception {
Util.checkBindingResult(bindingResult);
employeeService.createEmployee(employeeReq);
}
}
工具类:1.校验失败处理
public class Util {
public static void checkBindingResult(BindingResult bindingResult) throws Exception {
if (bindingResult == null) {
return;
}
if (bindingResult.hasErrors()) {
String errorMessage = bindingResult.getAllErrors().get(0).getDefaultMessage();
if (errorMessage != null && errorMessage.length() > 50) {
errorMessage = "参数错误";
}
throw new ComacException(errorMessage);
}
}
}
定义处理校验失败异常类:2.全局处理
1.定义处理全局异常类
//集中处理异常
@Slf4j
//@RestControllerAdvice(basePackages = "cn.jinronga.gulimall.product.controller")
//@ResponseBody
@RestControllerAdvice(basePackages = "cn.jinronga.gulimall.product.controller")
public class GuilimallExceptionControllerAdvice {
@ExceptionHandler(value= MethodArgumentNotValidException.class)
public R handleVaildException(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError)->{
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(), BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("错误:",throwable);
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
1.定义异常枚举类:
/***
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
*
*
*/
public enum BizCodeEnume {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验失败");
private int code;
private String msg;
BizCodeEnume(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
JSR303定义的校验类型
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 检查数字是否介于min和max之间.
@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
分组校验:
步骤:
-
在校验注解上加上
groups = {xxx.class, ...}
属性,值可以是任意interface接口,例如@URL(message = "logo必须是一个合法的url地址",groups={AddGroup.class,UpdateGroup.class})
; -
在开启校验处,将
@Valid
注解改为@Validated({xxx.class})
,例如@Validated({AddGroup.class})
就表示只校验该组的属性;注意:未添加任何分组的校验将会无效,开启娇艳的时候i如果添加了分组信息,那么只会校验同样页添加了该分组的属性。
案例:
定义两个空接口作为标记:
public interface AddGroup {
}
public interface UpdateGroup {
}
实体类:groups = {xxx.class}分组校验
@Data
@TableName("pms_brand")
@Validated //数据校验
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@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;
/**
* 品牌logo地址
*/
@NotEmpty(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的url地址",groups = {AddGroup.class,UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty(groups = {AddGroup.class})
@Pattern(regexp = "[a-zA-Z]$",message = "检索首字母必须是一个字母",groups = {AddGroup.class,UpdateGroup.class} )
private String firstLetter;
/**
* 排序
*/
@NotNull(groups = {AddGroup.class})
@Min(value = 0,message = "排序必须大于0",groups = {AddGroup.class,UpdateGroup.class})
private Integer sort;
}
将@Valid
注解改为@Validated({xxxx.class})
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand/*, BindingResult bindingResult*/){
brandService.save(brand);
return R.ok();
}
注:
分组校验(多场景复杂校验)
-
@NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class})
给校验注解标注什么情况需要进行校验 -
@Validated({AddGroup.class})
**统一处理异常**
默认没有指定分组校验注解@NotBlank,在分组校验情况下不生效的,只会在@Validated下生效
自定义校验
JSR303 是 Java EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是hibernate Validator,有了它,我们可以在实体类的字段上标注不同的注解实现对数据的校验,不用 if-else
判断,简化了我们的开发,而且可读性也很好。
给字段 showStatus
上标注自定义注解 @ListValue
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue(vals={0,1},groups = AddGroup.class)
private Integer showStatus;
1、编写一个自定义的校验注解创建注解 @ListValue
,可以参考官方的注解,比如 @NotNull
,我们只需要修改下面注释的几处即可
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
//配置文件中错误提示信息的名称
String message() default "{cn.jinronga.gulimall.product.valid.ListValue}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
//自定义值的类型
int[] vals()default{};
}
2.创建自定义约束校验器,继承 ConstraintValidator
,第一个泛型是自定义注解、第二个是校验值的类型,也即注解标注的字段的类型
public class ListValueConstraintValidator 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);
}
}
//判断是否判断成功
/**
*
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
3.在 resources
目录下创建一个 ValidationMessages.properties
配置文件,key
是第二步 message
设置的默认值,value
是自定义错误信息
cn.jinronga.gulimall.product.valid.ListValue="必须自定的值"
自定义注解 @ListValue
就可以工作了,当然这只是很简单的校验,但方法大同小异。