目录
@Valid注解
案例说明
之前的做法
现在的做法
总结
先来看一下数据库表,发现标签名称和操作者id都允许为空,其实这里正常来讲应该设置为非空的。
tips:当数据库表设置为非空的时候,如果我们没有添加相应的非空字段,就不会添加成功!
但是这里,数据库中没有进行非空约束。于是,我们尝试添加为空的数据,肯定是会添加成功的。
@PostMapping("insert")
@ApiOperation(value = "新增行业标签",notes = "新增行业标签,这里是详细描述信息!",httpMethod = "POST")
public Result insert(Industry industry){
// todo 需要先校验对象
industryService.save(industry);
return Result.success();
}
但是这是我们不希望看到的,我们希望看到的是name和operator_id字段都有值。所以我们需要对入参进行校验。
其实,不管数据库有没有设置为空,我们都要进行入参的校验工作,加强保证数据的合理性。
校验请求参数,我们之前的一般做法都是:
获取到当前参数,然后进行判断,如果不满足条件,我们返回错误提示。
@PostMapping("insert")
@ApiOperation(value = "新增行业标签",notes = "新增行业标签,这里是详细描述信息!",httpMethod = "POST")
public Result insert(Industry industry){
String name = industry.getName();
Integer operatorId = industry.getOperatorId();
if (StringUtils.isEmpty(name)){
return Result.fail("标签名称不能为空");
}
if (operatorId == null){
return Result.fail("创建人不能为空");
}
industryService.save(industry);
return Result.success();
}
只需要在对应的形参添加@Valid注解,然后在对应的实体类属性添加对应约束的注解即可。
@PostMapping("insert")
@ApiOperation(value = "新增行业标签",notes = "新增行业标签,这里是详细描述信息!",httpMethod = "POST")
public Result insert(@Valid Industry industry){
industryService.save(industry);
return Result.success();
}
@ApiModelProperty(value = "标签名称")
@TableField("name")
@NotBlank(message = "标签名称不能为空")
private String name;
@ApiModelProperty("操作用户id")
@TableField("operator_id")
@NotNull(message = "创建人不能为空")
private Integer operatorId;
此时,我们测试一下
我们发现,也是无法添加成功的。直接报异常了~
我们查看一下,控制台打印的信息:
2022-10-01 12:39:19.285 WARN 7736 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'industry' on field 'operatorId': rejected value [null]; codes [NotNull.industry.operatorId,NotNull.operatorId,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [industry.operatorId,operatorId]; arguments []; default message [operatorId]]; default message [创建人不能为空]]
但是我们希望按照之前一样的统一返回结果的格式进行返回。
我们可以做一个统一异常处理!
@ControllerAdvice
public class GlobalException {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
return Result.fail("执行了全局异常处理");
}
}
再执行发现返回的结构一致了。
但是返回的消息msg,并不是我们@NotNull注解中的message内容
此时捕获的是全局异常类,那么针对这种特定的具体异常还可以进行单独处理!
所以我们需要针对特定异常,来单独处理!
我们通过控制台,可以看到异常的名称叫做BindException,那么我们就针对改异常进行处理~
@ControllerAdvice
public class GlobalException {
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error(Exception e){
e.printStackTrace();
return Result.fail("执行了全局异常处理");
}
@ExceptionHandler(BindException.class)
@ResponseBody
public Result error(BindException e){
e.printStackTrace();
return Result.fail("特定异常被执行");
}
}
现在,如何能够获取到@NotNull注解中的message值返回呢?
@ExceptionHandler(BindException.class)
@ResponseBody
public Result error(BindException e){
e.printStackTrace();
String defaultMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
return Result.fail(defaultMessage);
}
这样就可以了。
解析一下:
BindException这个参数校验异常,会把所有的异常结果放到BindingResult接口中。我们可以通过当前异常调用它内部方法,拿到它当前的所有异常结果,然后获取到第一条异常的message消息。
网上很多人的做法:
网上很多教程都是把BindingResult放在接口的参数中,然后在接口里面处理。
例如:
@RequestMapping("/save") public R save(@RequestBody @Valid BrandEntity brand, BindingResult result){ if (result.hasErrors()){ Map
map = new HashMap<>(); //1.获取校验的错误结果 result.getFieldErrors().forEach(item -> { // FieldError 获取到错误提示 String message = item.getDefaultMessage(); // 获取错误的属性的名字 String field = item.getField(); map.put(field,message); }); return R.error(400,"提交的数据不合法").put("data",map); }else { brandService.save(brand); return R.ok(); } } 测试:
{ "msg": "提交的数据不合法", "code": 400, "data": { "name": "品牌名不能为空,或者空格,或者空串", "logo": "品牌logo必须是一个合法的url", "sort": "排序字段必须大于等于0" } }
其实这样不是很好,但凡有校验的,每个接口都要传BindingResult,还要写处理逻辑。
不过我们可以参考一下,进行更改。
@ExceptionHandler(BindException.class) @ResponseBody public Result error(BindException e){ // e.printStackTrace(); // String defaultMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage(); BindingResult result = e.getBindingResult(); Map
map = new HashMap<>(); //1.获取校验的错误结果 result.getFieldErrors().forEach(item -> { // FieldError 获取到错误提示 String message = item.getDefaultMessage(); // 获取错误的属性的名字 String field = item.getField(); map.put(field,message); }); return Result.fail("参数类型异常",map); } { "code": 500, "msg": "参数类型异常", "data": { "name": "标签名称不能为空", "operatorId": "创建人不能为空" } }
@Valid注解主要用作就是参数校验。
对于简单的校验,省去了我们每次还得写大量重复代码的工作。
步骤:
添加依赖
添加注解@Valid到对应的控制器方法入参
在对应的入参实体类属性上添加对应的功能注解,比如@NotNull,其中message属性添加不匹配时的提示信息。
一旦不匹配会抛出异常,需设置统一异常处理。针对该参数校验的异常添加统一异常处理。
tips:
javax.validation 是基于JSR-303标准开发出来的,使用注解方式实现,及其方便,但是这只是一个接口,没有具体实现。
Hibernate-Validator是一个hibernate独立的包,可以直接引用,他实现了javax.validation同时有做了扩展,比较强大。
所以我们引入依赖需要这两个:
javax.validation validation-api 1.1.0.Final org.hibernate hibernate-validator 5.2.4.Final 当然,Spring有对该依赖进行整合,所以我们可以直接引入如下这个依赖即可。
org.springframework.boot spring-boot-starter-validation 2.7.3
常用注解:
限制 | 说明 |
@Null | 用在基本类型上;限制只能为null |
@NotNull | 用在基本类型上;不能为null,但可以为empty |
@NotEmpty | 用在集合类上面;不能为null,而且长度必须大于0 |
@NotBlank | 用在String上面;只能作用在String上,不能为null,而且调用trim()后,长度必须大于0 ps:意味着不能为null,不能为空串,还不能是只有空格的 |
@Max(value) | 限制必须为一个不大于指定值的数字 |
@Min(value) | 限制必须为一个不小于指定值的数字 |
@AssertFalse | 限制必须为false |
@AssertTrue | 限制必须为true |
@Pattern | 自定义规则,可以通过regexp属性传入正则表达式,例如regexp = "/^[a-zA-Z]$/",意思是要求只传一个字母 |
@Url | 这个注解是org.hibernate.validator.constraints下的,也可以使用,限制必须是一个合法的url地址 |
统一异常处理:
@Slf4j
@ControllerAdvice
public class GulimallException {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public R handleValidException(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError)->{
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(400,"数据校验出现问题").put("data",errorMap);
}
@ExceptionHandler(Throwable.class)
@ResponseBody
public R handleException(Throwable throwable){
return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(),BizCodeEnum.UNKNOW_EXCEPTION.getValue());
}
}