介绍
JSR303 JAVA SE6的一项数据校验规范;
周末时间在编写项目时,需要对起前端传递的数据进行校验,避免后期因为数据异常导致读写出错。我们可以通过在bean数据上增加一层注解的方式,实现对后端传递数据的校验。
上实体类:
com.zpy.entity.PmsBrandEntity,代码如下:
@Data @TableName("pms_brand") public class PmsBrandEntity implements Serializable { private static final long serialVersionUID = 1L; /** * 品牌id */ @TableId(type = IdType.AUTO) // 设置id自动增长 @NotBlank(message = "修改状态id字段不能为空") private Long brandId; /** * 品牌名 */ @NotNull(message = "品牌名称不能为空") private String name; /** * 品牌logo地址 */ @NotNull(message = "logo地址不能为空") @URL(message = "logo 必须是一个URL地址") private String logo; /** * 介绍 */ private String descript; /** * 显示状态[0-不显示;1-显示] */ @NotNull(message = "是否显示字段不能为空") @ListValue(groups = {AddInterface.class},vals = {0,1},message = "{com.zpy.exception.validate_annotation.ListValue.message}") private Integer showStatus; /** * 检索首字母 */ @NotNull(message = "该字段必填") @Pattern(regexp = "^[a-zA-Z]$", message = "首字母必须是大写或者小写字母") private String firstLetter; /** * 排序 */ @NotNull(message = "该字段必填") @Min(value = 0, message = "排序字段必须是一个大于等于0的整数") private Integer sort; } |
实体类上 使用的注解 @Blank @Null @Min @Pattern 注解,会对传递到后端的数据中的不同字段进行校验;如 sort 字段,上面标有注解 @NotNull 和@Min 注解,表示只有当传递进来的bean对象中,sort字段不为Null,并且最小值为0;如果sort字段为null的情况下,则提示信息“该字段必填”,如果sort字段 的值小于0,则提示“排序字段必须是一个大于等于0的整数”;
那么,该怎么去使用这些验证呢?
我在对应的controller方法上增加了注解@validate
如下:
public R save(@Valid @RequestBody PmsBrandEntity pmsBrand ) |
表示,当我的系统调用save方法,参数中传递pmsBrand 的entity实体参数,按照实体中不同注解的校验规则进行校验。对于数据中出现异常的数据,进行记录;
如果我想获取到哪些字段出现了哪些问题,有什么方法吗?
--- 可以在传参中定义 BindingResult result对象,具体实现方法如下:
public R save(@Valid @RequestBody PmsBrandEntity pmsBrand , BindingResult result) |
可以在方法中,将内容打印出来:
if (result.hasErrors()) { //判断是否有错误 result.getFieldErrors().forEach(item->{ String filed = item.getField(); // 获取到出错字段 String message = item.getDefaultMessage(); // 获取到出错时返回的信息 }); } |
BindResult 需要import的包: import org.springframework.validation.BindingResult;
注意:@NotBlank @NotEmpty @NotNull 的区别:
@NotNull 表示当前数据不为null值,但可以为empty,没有Size的约束
@NotEmpty 用在集合类上面 加了@NotEmpty的String类、Collection、Map、数组,是不能为null或者长度为0的(String Collection Map的isEmpty()方法)
@NotBlank只用于String,不能为null且trim()之后size>0;
注解中可以配置哪些参数呢?
通用的message: 表示当前字段校验出错的情况下,提示哪些信息;如果这个字段不做任何要求的情况下,那么当前提示信息设置为默认值;可以根据自己的要求进行设置,所有注解中都会有这个字段;
value:表示当前校验字段可以是哪些值;如@Min @Max中存在这个字段;
group:分组,下面会提到;
Message可以设置提示信息,我可以将它的信息设置到Properties配置文件中,message中可以直接引用其配置;创建的配置文件名称一定要叫:ValidationMessages.properties
在配置文件中:
com.zpy.exception.validate_annotation.ListValue.message=请填写对应的值 |
则在配置文件中,可以设置为:
@NotNull(message = "{com.zpy.exception.validate_annotation.ListValue.message}") private Integer sort |
分组校验
entity中,有些字段(如id)在新增的时候不需要做任何校验,但是在修改的时候,我需要对它进行非空验证;这个情况下需要如何处理呢?
我们可以进行验证的分组设置,以:
public R save(@Valid @RequestBody PmsBrandEntity pmsBrand ) |
为例,这里我们定义两个接口:
public interface AddInterface { } public interface Updateinterface { } |
这两个接口不需要做任何实现,什么作用呢?在下面的分组操作,仅仅起到一个标志位的作用;
我们将controller中的注解@Valid 切换成@Validated,并设置分组内容:
@Validated({AddInterface.class}) |
再执行save方法的时候,标记为AddInterface.class的字段才会进行验证;
Entity中的配置:
@Data @TableName("pms_brand") public class PmsBrandEntity implements Serializable { private static final long serialVersionUID = 1L; /** * 品牌id */ @TableId(type = IdType.AUTO) // 设置id自动增长 @NotBlank(message = "修改状态id字段不能为空", groups = {Updateinterface.class}) private Long brandId; /** * 品牌名 */ @NotNull(message = "品牌名称不能为空", groups = {Updateinterface.class, AddInterface.class}) private String name; /** * 品牌logo地址 */ @NotNull(message = "logo地址不能为空",groups = {Updateinterface.class, AddInterface.class}) @URL(message = "logo 必须是一个URL地址",groups = {Updateinterface.class, AddInterface.class}) private String logo; /** * 介绍 */ private String descript; /** * 显示状态[0-不显示;1-显示] */ @NotNull(message = "是否显示字段不能为空",groups = {Updateinterface.class, AddInterface.class}) @ListValue(groups = {AddInterface.class},vals = {0,1},message = "{com.zpy.exception.validate_annotation.ListValue.message}") private Integer showStatus; /** * 检索首字母 */ @NotNull(message = "该字段必填",groups = {Updateinterface.class, AddInterface.class}) @Pattern(regexp = "^[a-zA-Z]$", message = "首字母必须是大写或者小写字母",groups = {Updateinterface.class, AddInterface.class}) @NotEmpty private String firstLetter; /** * 排序 */ @NotNull(message = "该字段必填",groups = {Updateinterface.class, AddInterface.class}) @Min(value = 0, message = "排序字段必须是一个大于等于0的整数",groups = {Updateinterface.class, AddInterface.class}) private Integer sort; } |
注意一点,如果我们在程序中开启了分组校验,那么我们需要注意的是,entity中每个字段的每一种校验方式都需要增加group分组属性,如果存在校验方式注解中未标注group,那么在任何时候,都不会触发当前字段的校验;
对于id字段,我们可以可以设置注解 @NotBlank(message = "修改状态id字段不能为空", groups = {Updateinterface.class}),然后在controller中
public R update(@Validated({Updateinterface.class}) @RequestBody PmsBrandEntity pmsBrand ) |
增加分组信息,这样,当我们调用update方法对数据进行修改时,则会触发对id的校验;
自定义校验注解
如果给定的校验注解,无法满足日常需求,我们可以自定义校验注解,比如我想对showStatus字段增加校验,只允许它的值是0 或 1;
存在如下步骤:
- 创建自定义注解@ListValue,并为它增加对应的属性;
import com.zpy.exception.validate_constraint.ListValueConstraint;
import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Documented @Constraint(validatedBy = {})// 表示可以使用哪个类作为校验方式 @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) // 标志注解可以在哪里使用 @Retention(RUNTIME)// 什么时候执行 public @interface ListValue {
String message() default "{com.zpy.exception.validate_annotation.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default {}; } |
注意:message groups Payload 三个字段,为必要属性,必须增加;
vals属性为新增属性,我们在这里指定需要校验的字段可以是哪些值;
- 自定义校验类,需要实现Constraintvalidator 接口;
import com.zpy.exception.validate_annotation.ListValue;
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.HashSet; import java.util.Set; // 第一个泛型表示当前注解,第二泛型表示传入值的类型 public class ListValueConstraint implements ConstraintValidator<ListValue,Integer> { private Set<Integer> set = new HashSet<>(); @Override public void initialize(ListValue constraintVals) {// 初始化方法 int[] vals = constraintVals.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); } } |
- 将自定义校验注解和校验类关联起来;
在自定义注解中@Constraint(validatedBy = {}) 增加validator 类
@Constraint(validatedBy = {ListValueConstraint.class}) |
附: 校验注解
摘自知乎: JSR303校验 - 知乎 (zhihu.com) ,作者:知者天天学习
空检查
@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 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。
数值检查
建议使用在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=) 被指定的元素必须在合适的范围内
@Range(min=10000,max=50000,message=”range.bean.wage”)
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)