基本校验方式
基本校验方式二
基本校验方式三
@Validated和@Valid区别
validater 系列详解
@Validated和@Valid的区别
如题的问题,我相信是很多小伙伴都很关心的一个对比,若你把这个系列都有喵过,那么这个问题的答案就浮出水面了:
@Valid:标准JSR-303规范的标记型注解,用来标记验证属性和方法返回值,进行级联和递归校验
@Validated:Spring的注解,是标准JSR-303的一个变种(补充),提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制
在Controller中校验方法参数时,使用@Valid和@Validated并无特殊差异(若不需要分组校验的话)
@Validated注解可以用于类级别,用于支持Spring进行方法级别的参数校验。@Valid可以用在属性级别约束,用来表示级联校验。
@Validated只能用在类、方法和参数上,而@Valid可用于方法、字段、构造器和参数上
1 自动校验:自动验证分组时,要传入结果绑定参数BindingResult,用来接收异常的结果
@SysLog("修改sysBusiness")
@ApiOperation("修改")
@PutMapping("{id}")
@RequiresPermissions("sys:business:update")
public Result update(@ApiParam(value = "服务实体对象serverEntity", required = true) @RequestBody @Valid SysBusinessEntity sysBusiness, BindingResult result,
@ApiParam(value = "主键ID", required = true) @PathVariable("id") Long id) {
//必填参数校验
if (result.hasErrors()){
return Result.fail(1,result.getFieldError().getDefaultMessage());
}
if (sysBusiness.getLat() != null && sysBusiness.getLng() != null) {
String point = MapUtils.ConvertPointString(sysBusiness.getLat(), sysBusiness.getLng());
sysBusiness.setPoint(point);
}
if (NumberUtils.notZero(id)) {
sysBusiness.setId(id);
}
//校验类型
ValidatorUtils.validateEntity(sysBusiness);
//更新商户信息
sysBusinessService.updateOne(sysBusiness);
//同步部门信息
sysBusinessService.syncDeptInfo(sysBusiness);
return Result.success();
}
@SysLog("保存volunteerActivity")
@ApiOperation("保存")
@PostMapping
@RequiresPermissions("volunteer:activity:save")
// 自动验证分组时,要传入结果绑定参数BindingResult,用来接收异常的结果
public Result save(@RequestBody @Valid ActivityEntity activity, BindingResult results) {
//校验非空
if (results.hasErrors()){
return Result.fail(1, Objects.requireNonNull(results.getFieldError()).getDefaultMessage());
}
return volunteerActivityService.addBusiness(activity);
}
实体类如下:
public class ActivityEntity extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
@ApiModelProperty(value = "主键Id")
private Long id;
@ApiModelProperty(value = "活动标题", required = true)
@NotBlank(message = "活动名称不能为空")
private String title;
@ApiModelProperty(value = "活动类型 1活动 2义演 3培训", required = true)
@NotNull(message = "活动类型不能为空")
private Integer type;
@ApiModelProperty(value = "活动类型名称")
@TableField(exist = false)
private String typeName;
@ApiModelProperty(value = "所属部门id", readOnly = true)
private Long deptId;
@ApiModelProperty(value = "活动地址", required = true)
@NotBlank(message = "活动地址不能为空")
private String address;
在参数输入时,输入了,type设置为空
{
"address": "string",
"communityIds": [
0
],
"details": "string",
"endDate": "2017-01-01 00:00:00",
"id": 0,
"managerId": 0,
"photo": "string",
"reviewUrl": "string",
"signEndDate": "2017-01-01 00:00:00",
"signNum": 0,
"startDate": "2017-01-01 00:00:00",
"title": "string",
"type": null,
"typeName": "string"
}
得出的结论是:活动类型不能为空,与该字段的message的value相同
{
"code": 1,
"msg": "活动类型不能为空",
"currentTime": "2019-11-11 09:40:17",
"results": null
}
设置:title 和type 都为空
title是String形式,设置为空是"",相应的属性字段的注解是@NotBlank
type是Integer形式,设置为空是null,相应的属性字段的注解是@null
{
"address": "string",
"communityIds": [
0
],
"details": "string",
"endDate": "2017-01-01 00:00:00",
"id": 0,
"managerId": 0,
"photo": "string",
"reviewUrl": "string",
"signEndDate": "2017-01-01 00:00:00",
"signNum": 0,
"startDate": "2017-01-01 00:00:00",
"title": "",
"type": null,
"typeName": "string"
}
会返回第一次获取的异常
{
"code": 1,
"msg": "活动名称不能为空",
"currentTime": "2019-11-11 10:02:53",
"results": null
}
如果把一些验证字段添加分组,如下所示,title字段加入UpdateGroup.class组别
@ApiModel(description = "志愿者活动表")
@TableName("volunteer_activity")
public class ActivityEntity extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
@ApiModelProperty(value = "主键Id")
private Long id;
@ApiModelProperty(value = "活动标题", required = true)
@NotBlank(message = "活动名称不能为空",groups = UpdateGroup.class)
private String title;
@ApiModelProperty(value = "活动类型 1活动 2义演 3培训", required = true)
@NotNull(message = "活动类型不能为空")
private Integer type;
@ApiModelProperty(value = "活动类型名称")
@TableField(exist = false)
private String typeName;
@ApiModelProperty(value = "所属部门id", readOnly = true)
private Long deptId;
@ApiModelProperty(value = "活动地址", required = true)
@NotBlank(message = "活动地址不能为空")
private String address;
同样输入如下:
{
"address": "string",
"communityIds": [
0
],
"details": "string",
"endDate": "2017-01-01 00:00:00",
"id": 0,
"managerId": 0,
"photo": "string",
"reviewUrl": "string",
"signEndDate": "2017-01-01 00:00:00",
"signNum": 0,
"startDate": "2017-01-01 00:00:00",
"title": "",
"type": null,
"typeName": "string"
}
{
"code": 1,
"msg": "活动类型不能为空",
"currentTime": "2019-11-11 10:08:12",
"results": null
}
可以发现,该字段无法验证(变成AddGroup和Group分组同样无法验证),说明@valid无法验证进行分组验证(@Validated)效果一样,如需分组校验,可以使用以下两种方法,
1 使用@Validated(UpdateGroup.class) 注解,如下所示:
@SysLog("保存volunteerActivity")
@ApiOperation("保存")
@PostMapping
@RequiresPermissions("volunteer:activity:save")
public Result save(@RequestBody @Validated(UpdateGroup.class) ActivityEntity activity, BindingResult results) {
//校验非空
if (results.hasErrors()){
return Result.fail(1, Objects.requireNonNull(results.getFieldError()).getDefaultMessage());
}
return volunteerActivityService.addBusiness(activity);
}
还是title 和type 都为空,由于title 在updated分组中,会进行验证
{
"code": 1,
"msg": "活动名称不能为空",
"currentTime": "2019-11-11 14:46:10",
"results": null
}
而如果title 不为空 typeName为空,则不进行typeName校验,因为typeName不在UpdateGroup组中
{
"address": "string",
"communityIds": [
0
],
"details": "string",
"endDate": "2022-01-01 00:00:00",
"id": 0,
"managerId": 0,
"photo": "string",
"reviewUrl": "string",
"signEndDate": "2021-01-01 00:00:00",
"signNum": 0,
"startDate": "2021-01-01 00:00:00",
"title": "string",
"type": 22,
"typeName": ""
}
@TableId
@ApiModelProperty(value = "主键Id")
private Long id;
@ApiModelProperty(value = "活动标题", required = true)
@NotBlank(message = "活动名称不能为空",groups = UpdateGroup.class)
private String title;
@ApiModelProperty(value = "活动类型 1活动 2义演 3培训", required = true)
@NotNull(message = "活动类型不能为空")
private Integer type;
@ApiModelProperty(value = "活动类型名称")
@TableField(exist = false)
@NotBlank(message = "活动类型名称不能为空")
private String typeName;
{
"code": 0,
"msg": "SUCCESS",
"currentTime": "2019-11-11 15:02:39",
"results": {
"createDate": "2019-11-11 15:02:39",
"id": "32",
"title": "string",
"type": 22,
"typeName": "志愿者活动",
"deptId": "56",
"address": "string",
"photo": "string",
"startDate": "2021-01-01 00:00:00",
"endDate": "2022-01-01 00:00:00",
"signEndDate": "2021-01-01 00:00:00",
"signNum": 0,
"details": "string",
"managerId": "0",
"reviewUrl": "string",
"communityIds": [
"0"
]
}
}
2 需要进行下面的手动校验
① 验证未分组的
@SysLog("修改活动报名具体项")
@PutMapping("{itemId}")
@ApiOperation("修改活动报名具体项")
@RequiresPermissions("aged:activitysignitem:update")
public Result update(
@ApiParam(value = "活动报名具体项实体ActivitySignitemEntity",required = true) @RequestBody ActivitySignitemEntity activitySignitem,
@ApiParam(value = "活动报名具体项主键itemId",required = true) @PathVariable("itemId") Long itemId) {
if(NumberUtils.notZero(itemId)) {
activitySignitem.setItemId(itemId);
}
ValidatorUtils.validateEntity(activitySignitem);
activitySignitemService.updateAllColumnById(activitySignitem);//全部更新
return Result.success();
}
我们把上面的SysUser 的验证改成,不带分组的验证,如下所示
@ApiOperation("保存用户")
@SysLog("保存用户")
@PostMapping
// @RequiresPermissions("sys:user:save")
public Result<SysUserEntity> save(@RequestBody SysUserEntity user) {
ValidatorUtils.validateEntity(user);
sysUserService.save(user);
return Result.success(user);
}
输入中,用户名和密码都为空
{
"createTime": "2018-11-11 11:11:11",
"defaultFamilyId": 0,
"defaultLinkAddressId": 0,
"deptId": 0,
"email": "a@a.com",
"extid": 0,
"mobile": 13000000000,
"name": "",
"password": "",
"position": 1,
"roleIdList": [
0
],
"sourceChannel": 0,
"status": 0,
"userId": 0,
"username": "admin3434343",
"usetype": 0,
"usetypeName": "string"
}
实体类中,账号和密码都在分组验证中,由于手动验证中未包含分组信息,表示只验证未分组的信息
@ApiModelProperty(value = "用户主键ID", allowEmptyValue = true)
@TableId
private Long userId;
@ApiModelProperty(value = "用户名", example = "admin")
@NotBlank(message="用户名不能为空", groups = {AddGroup.class, UpdateGroup.class})
private String username;
@ApiModelProperty(value = "密码", example = "000")
@NotBlank(message="密码不能为空", groups = AddGroup.class)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
@ApiModelProperty(value = "盐", example = "abcde", hidden = true)
@JsonIgnore
private String salt;
@ApiModelProperty(value = "邮箱", example = "a@a.com")
@Email(message = "邮箱格式不正确")
private String email;
@ApiModelProperty(value = "手机号", example = "13000000000")
@NotBlank(message = "手机号不能为空")
private String mobile;
存入成功:
{
"code": 0,
"msg": "SUCCESS",
"currentTime": "2019-11-11 10:50:13",
"results": {
"userId": "341",
"username": "admin3434343",
"email": "a@a.com",
"mobile": "13000000000",
"status": 0,
"roleIdList": [
"0"
],
"createTime": "2019-11-11 10:50:13",
"deptId": "0",
"deptName": null,
"usetype": 0,
"usetypeName": "string",
"position": 1,
"sourceChannel": 0,
"extid": "0",
"name": "",
"familyAddresses": null,
"defaultFamilyId": "0",
"defaultAddress": null,
"defaultLinkAddressId": "0",
"defaultLinkAddress": null,
"dossierId": null
}
}
邮箱格式不正确,并且在未分组的里面所以会报异常
{
"createTime": "2018-11-11 11:11:11",
"defaultFamilyId": 0,
"defaultLinkAddressId": 0,
"deptId": 0,
"email": "aa.com",
"extid": 0,
"mobile": 13000000000,
"name": "",
"password": "",
"position": 1,
"roleIdList": [
0
],
"sourceChannel": 0,
"status": 0,
"userId": 0,
"username": "admin3434343",
"usetype": 0,
"usetypeName": "string"
}
{
"code": 500,
"msg": "邮箱格式不正确",
"currentTime": "2019-11-11 11:03:15",
"results": null
}
3、手动校验,分组验证
验证条件换成下面的形式
@ApiOperation("保存用户")
@SysLog("保存用户")
@PostMapping
// @RequiresPermissions("sys:user:save")
public Result<SysUserEntity> save(@RequestBody SysUserEntity user) {
ValidatorUtils.validateEntity(user, AddGroup.class);
sysUserService.save(user);
return Result.success(user);
}
还是用户名、密码为空,邮箱格式不正确
用户名、密码不为空,邮件格式不正确时,保存成功,因为邮件字段并未在校验分组中,没有验证
{
"createTime": "2018-11-11 11:11:11",
"defaultFamilyId": 0,
"defaultLinkAddressId": 0,
"deptId": 0,
"email": "aa.com",
"extid": 0,
"mobile": 13000000000,
"name": "",
"password": "242424243",
"position": 1,
"roleIdList": [
0
],
"sourceChannel": 0,
"status": 0,
"userId": 0,
"username": "admin343434334535",
"usetype": 0,
"usetypeName": "string"
}
{
"code": 0,
"msg": "SUCCESS",
"currentTime": "2019-11-11 11:46:36",
"results": {
"userId": "343",
"username": "admin343434334535",
"email": "aa.com",
"mobile": "13000000000",
"status": 0,
"roleIdList": [
"0"
],
"createTime": "2019-11-11 11:46:36",
"deptId": "0",
"deptName": null,
"usetype": 0,
"usetypeName": "string",
"position": 1,
"sourceChannel": 0,
"extid": "0",
"name": "",
"familyAddresses": null,
"defaultFamilyId": "0",
"defaultAddress": null,
"defaultLinkAddressId": "0",
"defaultLinkAddress": null,
"dossierId": null
}
}
用户名为空的话,就是进行验证,因为用户名在addGroup分组里面:
{
"createTime": "2018-11-11 11:11:11",
"defaultFamilyId": 0,
"defaultLinkAddressId": 0,
"deptId": 0,
"email": "aa.com",
"extid": 0,
"mobile": 13000000000,
"name": "",
"password": "242424243",
"position": 1,
"roleIdList": [
0
],
"sourceChannel": 0,
"status": 0,
"userId": 0,
"username": "",
"usetype": 0,
"usetypeName": "string"
}
{
"code": 500,
"msg": "用户名不能为空",
"currentTime": "2019-11-11 12:45:14",
"results": null
}
4 分组校验的扩张
① validate(user, User.GroupA.class,User.GroupB.class) 多重分组参数,表示第A和B分组都进行验证,参数的顺序没有区别,都输出同样的结果
package com.javabase.javabase.beanvalidatedTest;
import javax.validation.GroupSequence;
import javax.validation.constraints.NotEmpty;
import javax.validation.groups.Default;
/**
* @Author ShawnYang
* @Date 2019-11-11 15:18
* @Description TODO
* 修改人:
* 修改时间:
* 修改备注:
*/
public class User {
@NotEmpty(message = "firstname may be empty")
private String firstname;
@NotEmpty(message = "middlename may be empty", groups = Default.class)
private String middlename;
@NotEmpty(message = "lastname may be empty", groups = GroupA.class)
private String lastname;
@NotEmpty(message = "country may be empty", groups = GroupB.class)
private String country;
public interface GroupA {
}
public interface GroupB {
}
// 组序列
@GroupSequence({Default.class, GroupA.class, GroupB.class})
public interface Group {
}
}
public class BeanvalidatedTest {
@Test
public void test(){
User user = new User();
// 此处指定了校验组是:User.Group.class
Set<ConstraintViolation<User>> result = Validation.buildDefaultValidatorFactory().getValidator().validate(user, User.GroupA.class,User.GroupB.class);
// 对结果进行遍历输出
result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println);
}
}
结果:
country country may be empty: null
lastname lastname may be empty: null
②、groups = {GroupB.class,GroupA.class}, 表示不管是对GroupA还是对GroupB进行分组校验,都需要对该字段进行校验
public class User {
@NotEmpty(message = "firstname may be empty")
private String firstname;
@NotEmpty(message = "middlename may be empty", groups = {GroupB.class,GroupA.class})
private String middlename;
@NotEmpty(message = "lastname may be empty", groups = GroupA.class)
private String lastname;
@NotEmpty(message = "country may be empty", groups = GroupB.class)
private String country;
public interface GroupA {
}
public interface GroupB {
}
// 组序列
@GroupSequence({Default.class, GroupA.class, GroupB.class})
public interface Group {
}
}
public class BeanvalidatedTest {
@Test
public void test(){
User user = new User();
// 此处指定了校验组是:User.Group.class
Set<ConstraintViolation<User>> result = Validation.buildDefaultValidatorFactory().getValidator().validate(user, User.GroupA.class);
// 对结果进行遍历输出
result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println);
// 此处指定了校验组是:User.Group.class
Set<ConstraintViolation<User>> result1 = Validation.buildDefaultValidatorFactory().getValidator().validate(user, User.GroupB.class);
System.out.println();
// 对结果进行遍历输出
result1.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println);
}
}
结果:
middlename middlename may be empty: null
lastname lastname may be empty: null
middlename middlename may be empty: null
country country may be empty: null
③ .validate(user, User.Group.class) 形式,按照Group组序列顺序进行验证
public class User {
@NotEmpty(message = "firstname may be empty")
private String firstname;
@NotEmpty(message = "middlename may be empty", groups = Default.class)
private String middlename;
@NotEmpty(message = "lastname may be empty", groups = GroupA.class)
private String lastname;
@NotEmpty(message = "country may be empty", groups = GroupB.class)
private String country;
public interface GroupA {
}
public interface GroupB {
}
// 组序列
@GroupSequence({Default.class, GroupA.class, GroupB.class})
public interface Group {
}
}
*/
public class BeanvalidatedTest {
@Test
public void test() {
User user = new User();
// 此处指定了校验组是:User.Group.class
Set<ConstraintViolation<User>> result = Validation.buildDefaultValidatorFactory().getValidator().validate(user, User.Group.class);
// 对结果进行遍历输出
result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println);
}
}
结果:注意firstname也进行了验证,因为分组为空,则表示Default.class
middlename middlename may be empty: null
firstname firstname may be empty: null
如果是下面的形式:
public class BeanvalidatedTest {
@Test
public void test() {
User user = new User();
user.setFirstname("f");
user.setMiddlename("s");
// 此处指定了校验组是:User.Group.class
Set<ConstraintViolation<User>> result = Validation.buildDefaultValidatorFactory().getValidator().validate(user, User.Group.class);
// 对结果进行遍历输出
result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue()).forEach(System.out::println);
}
}
则得到的结果是:
lastname lastname may be empty: null
现象:Default组都校验通过后,执行了GroupA组的校验。但GroupA组校验木有通过,GroupB组的校验也就不执行了~
@GroupSequence提供的组序列顺序执行以及短路能力,在很多场景下是非常非常好用的。
所以@Group 的意思是对Group的组进行验证,并按照顺序进行验证。