springboot 使用校验框架validation校验

基本校验方式
基本校验方式二
基本校验方式三
@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的组进行验证,并按照顺序进行验证。

分组验证的参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值