SpringBoot中注解数据校验,分组校验,自定义注解校验,同一异常处理(JSR303规范)

在我们实际编程中,几乎都会使用到后端数据校验功能,是SpringBoot中,更是简化了这一操作,让我们来简单看一下!
本教程仅作为我个人的学习记录,避免遗忘,方便下次快速使用,如有错误,请指出,谢谢观看~

本文完整示例代码下载:https://download.csdn.net/download/qq_42628989/13125444

一、SpringBoot模拟使用场景

我们模拟这么一个场景:在系统中添加一本新的图书

@Data
public class BookModel {

    private String id;

    private String name;

    private String coverUrl;

    private String describe;

    private Integer isDelete;

}
@RestController
@Slf4j
public class BookController {

    @PostMapping("/addBook")
    public Map<String,Object> addBook(@RequestBody BookModel book)
    {
        /* book....... 一系列数据库操作*/
        log.error("book.id:{}",book.getId());
        log.error("book.name:{}",book.getName());
        log.error("book.coverUrl:{}",book.getCoverUrl());
        log.error("book.describe:{}",book.getDescribe());
        log.error("book.isDelete:{}",book.getIsDelete());
        return returnTools(200,"书籍信息录入成功!");
    }


    public Map<String,Object> returnTools(Integer code,String msg)
    {
        Map<String,Object> map = new HashMap<>();
        map.put("code",code);
        map.put("msg",msg);
        return map;
    }


}

然后我们来测试访问这个接口:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到图书信息被正常保存,并且成功返回信息!

但是在这里我们可以发现,我们并没有输入全部信息,接口也成功执行,并成功返回数据,这在很多情境下是不被允许的!

所以在我们的代码中经常可以看到无数个if(){}else{}组成的校验代码,可以使用,但是代码量非常多,并且每个需要数据校验的Controller中都需要手动调用。

所以接下在我们就介绍一下Jsr303规范下的validation数据校验框架!

二.数据校验简单使用

1.简单使用注解校验

我们直接在需要校验的Model字段上加上条件即可

@Data
public class BookModel {

    @NotNull(message = "请输入书籍ID")
    private String id;

    @NotNull(message = "请输入书籍名称")
    private String name;

    @NotNull(message = "封面图片链接必须填写")
    @URL(message = "封面必须为URL格式")
    private String coverUrl;

    @NotNull(message = "请填写书籍简介")
    @Length(min = 5,message = "简介最小长度为5")
    private String describe;

    @NotNull(message = "isDelete字段不存在")
    private Integer isDelete;

}

然后在需要校验的Controller上加入@Valid注解即可!

在这里插入图片描述
我们再次进行测试:
在这里插入图片描述
此时我们访问该接口就可以发现,校验注解已生效,返回了错误信息。

2.自定义校验错误处理

但是在我们的实际项目中,这样的返回格式显然不是我们想要的,我们需要返回自定义格式,这时只需要在需要校验的模型后紧跟BindingResult对象即可:

   @PostMapping("/addBook")
    public Map<String,Object> addBook(@Valid @RequestBody BookModel book, BindingResult result)
    {
        if(result.hasErrors())
        {
            return returnTools(500,"书籍信息输入不全~");
        }
        else {
            /* book....... 一系列数据库操作*/
            log.error("book.id:{}", book.getId());
            log.error("book.name:{}", book.getName());
            log.error("book.coverUrl:{}", book.getCoverUrl());
            log.error("book.describe:{}", book.getDescribe());
            log.error("book.isDelete:{}", book.getIsDelete());
            return returnTools(200, "书籍信息录入成功!");
        }
    }

我们再来测试一下:

在这里插入图片描述
此时就可以发现自定义返回信息生效!

注意:
此方法虽能有效解决数据校验问题,但是需要侵入式较强的修改代码,且每一个需要校验的Controller都需要修改,比较繁琐,不推荐使用,在此不过多介绍!

3.全局统一异常处理

使用SpringBoot的注解校验后,在校验异常时,会自动触发MethodArgumentNotValidException异常,我们只需要使用SpringBoot中的全局异常处理接收即可!

我们将工具类ReturnTools提取为公共方法:
在这里插入图片描述
并建立全局异常处理类:

@RestControllerAdvice
public class ExceptionHandler {

    @org.springframework.web.bind.annotation.ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Map<String,Object> handleVaildException(MethodArgumentNotValidException e)
    {
        BindingResult bindingResult = e.getBindingResult();
        Map<String,Object> map = new HashMap<>();
        bindingResult.getFieldErrors().forEach(error -> {
            map.put(error.getField(),error.getDefaultMessage());
        });
        return ReturnTools.returnTools(10001, "书籍信息录入不全",map);
    }

    @org.springframework.web.bind.annotation.ExceptionHandler(value = Throwable.class)
    public Map<String,Object> handleAllException(Throwable t)
    {
        return ReturnTools.returnTools(10000,"系统未知异常",null);
    }
}

删除以前的错误处理逻辑,只保留业务逻辑即可:

在这里插入图片描述

开始测试:

在这里插入图片描述

测试发现:我们不但可以返回自定义的数据格式,还可以更灵活的取得校验错误信息,是最推荐使用的异常处理方式!

三、分组校验

在我们的实际项目中,增加和修改需要校验的规则不是相同的:

  • 在新增时,不可以填写ID字段,其他信息必须填写
  • 在更新时,必须填写ID字段,其他信息可以选择性填写

这时候就是用到了分组校验这功能:
在这里插入图片描述

在分组校验中,规定了一个分组必须是一个接口,这个接口为空接口就可以,只是用来识别使用哪一种校验规则!

例如:我们添加一个Update接口吗,实现添加书籍和修改书籍时分别使用两种不同的校验规则!

  1. 首先建立两个接口,用于区别新增和修改(名称可以自定义,自己可分辨即可,并且无需实现)
    在这里插入图片描述

2.在Model中编写第二套校验逻辑,并使用groups字段区分!

@Data
public class BookModel {

    @Null(message = "新增时不能传入书籍ID",groups = {AddGroup.class})
    @NotNull(message = "更新时必须传入书籍ID",groups = {UpdateGroup.class})
    private String id;

    @NotNull(message = "请输入书籍名称",groups = {AddGroup.class})
    private String name;

    @NotNull(message = "封面图片链接必须填写",groups = {AddGroup.class})
    @URL(message = "封面必须为URL格式",groups = {AddGroup.class,UpdateGroup.class})
    private String coverUrl;

    @NotNull(message = "请填写书籍简介",groups = {AddGroup.class})
    @Length(min = 5,message = "简介最小长度为5",groups = {AddGroup.class,UpdateGroup.class})
    private String describe;

    @NotNull(message = "isDelete字段不存在",groups = {AddGroup.class})
    private Integer isDelete;

}

注意:

  • 一个注解中可填写多个分组,比如“coverUrl和describe”,此时校验规则在新增和修改时都生效
  • “coverUrl”字段中,我们同事加入了两个校验注解,此时“@URL”注解在新增和更新时都生效,但“ @NotNull”注解之在更新时生效,换句话说:在更新时可以不填写“coverUrl”字段,但是如果填写,则必须为URL的格式(“describe”字段同理,更新是可以不填写,但填写则必须长度大于5)

3.在Controller中加入分组校验注解并指定分组

@RestController
@Slf4j
public class BookController {

    @PostMapping("/addBook")
    public Map<String,Object> addBook(@Validated(value = {AddGroup.class}) @RequestBody BookModel book)
    {
        /* book....... 一系列数据库操作*/
        log.error("add:book.id:{}", book.getId());
        log.error("add:book.name:{}", book.getName());
        log.error("add:book.coverUrl:{}", book.getCoverUrl());
        log.error("add:book.describe:{}", book.getDescribe());
        log.error("add:book.isDelete:{}", book.getIsDelete());
        return ReturnTools.returnTools(200, "书籍信息录入成功!",null);
    }

    @PostMapping("/updateBook")
    public Map<String,Object> updateBook(@Validated(value = {UpdateGroup.class}) @RequestBody BookModel book)
    {
        /* book....... 一系列数据库操作*/
        log.error("update:book.id:{}", book.getId());
        log.error("update:book.name:{}", book.getName());
        log.error("update:book.coverUrl:{}", book.getCoverUrl());
        log.error("update:book.describe:{}", book.getDescribe());
        log.error("update:book.isDelete:{}", book.getIsDelete());
        return ReturnTools.returnTools(200, "书籍信息更新成功!",null);
    }

}

此时,如果访问addBook接口,则新增校验逻辑生效(除了ID其他必须不为空,ID必须为空)
如果访问updateBook接口,则更新校验逻辑生效(ID必须不为空,其他可以为空)

4.测试
在这里插入图片描述
在这里插入图片描述
测试结果完全正确!

四、自定义校验注解

在这里插入图片描述
在这个字段中,我们想让他赋值为0时显示,赋值为1时删除,但是我们并没有这样的注解供我们使用(也可以使用正则表达式和最大最小值等,但是为了做笔记嘛,假装没有~)

我们来字写一个注解@IsDelete,用来验证字段的值是不是1或0

在这里插入图片描述
其中,上方元注解不变即刻,下方的“message,groups,payload”为固定格式,直接复制即可!

我们使用Values指定符合条件的值,如果当前字段的值在数组中,则通过校验。

注解编写完成后,还需要编写相应的实现类,然后进行配置!

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
        validatedBy = {isDeleteImpl.class}
)
public @interface IsDelete {

    /*这三条为固定格式,必须填写*/
    String message() default "值不符合要求";
    Class<?> [] groups() default {};
    Class<? extends Payload> [] payload() default {};

    int[] value();//如果当前数组包括字段的值则校验成功

}
public class isDeleteImpl implements ConstraintValidator<IsDelete, Integer> {
    //两个参数
    // 1.自定义注解的类型
    //2.自定义注解修饰的目标的类型

    int[] values;

    @Override
    public void initialize(IsDelete constraintAnnotation) {
        values = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {

        if(values == null)
        {
            return false;
        }
         if(integer == null)
        {
            return true;
        }
        for (int value : values) {
            if(value == integer.intValue())
            {
                return true;
            }
        }
        return false;
        //通过校验返回True,校验失败返回False
    }
}

在这里插入图片描述
注意:不要忘了进行配置关联

然后进行测试:
在这里插入图片描述
可以发现测试成功!

4.更多校验注解(转载)

注解	作用类型	       解释
@NotNull	   任何类型  	         属性不能为null
@NotEmpty	   集合	                 集合不能为null,且size大于0
@NotBlanck	   字符串、字符  	     字符类不能为null,且去掉空格之后长度大于0
@AssertTrue	   Boolean、boolean	     布尔属性必须是true
@Min	       数字类型(原子和包装)  限定数字的最小值(整型)
@Max@Min	             限定数字的最大值(整型)
@DecimalMin@Min	             限定数字的最小值(字符串,可以是小数)
@DecimalMax@Min	             限定数字的最大值(字符串,可以是小数)
@Range	       数字类型(原子和包装)  限定数字范围(长整型)
@Length	       字符串	             限定字符串长度
@Size	       集合	                 限定集合大小
@Past	       时间、日期	         必须是一个过去的时间或日期
@Future	       时期、时间	         必须是一个未来的时间或日期
@Email	      字符串	             必须是一个邮箱格式
@Pattern	 字符串、字符	         正则匹配字符串
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值