实际业务中,我们是离不开数据的校验的。比如注册用户,用户名和密码是不能为空的。
今天的分享,我用一个简单的使用 Spring Boot+MyBatis 程序添加用户为例,来进行讲解。
1,平常我们进行数据校验
例如这个添加用户的查询,我们要将用户类User
的实例添加进数据库,代码如下:
首先是User
类:
packagecom.example.validationtest.dataobject; importlombok.Getter; importlombok.NoArgsConstructor; importlombok.Setter; importjava.io.Serializable; /*** 用户类*/publicclassUserimplementsSerializable { /*** 主键id*/privateIntegerid; /*** 用户名*/privateStringusername; /*** 密码*/privateStringpassword; /*** 邮箱*/privateStringemail; /*** 年龄*/privateintage; }
我们会在Service
层里面调用dao
来把数据添加进数据库,不过我们会在Service
类里面写很多校验字段的东西,例如这个添加用户的方法片段:
privateUserDAOuserDAO; publicStringadd(Useruser) { if (StringUtils.isEmpty(user.getUsername())) { return"用户名不能为空!"; } if (StringUtils.isEmpty(user.getPassword())) { return"密码不能为空!"; } ... userDAO.add(user); return"添加成功!"; }
最后再使用Controller
处理请求进行添加。
这样的逻辑没有错,但是当实体类多起来了,那就很麻烦了,要对每个字段进行if
判断校验,不仅麻烦,而且代码冗余。
因此我们需要用到Spring Validation来解决这个问题。
2,配置Validation
在新建Spring Boot工程的时候,我们可以勾上Validation
这个依赖。
也可以后续手动在pom.xml
里面添加依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
3,使用注解对实体类的字段设定校验规则
Validation
提供了很多校验规则注解,将其注解在字段上以设定其校验规则,这里列出几个常用的:
@NotEmpty
对象不允许为null或者空,例如字符串长度为0,集合、Map等对象长度为0,都判为空@NotBlank
不允许为null和纯空格@NotNull
不允许为空对象@AssertTrue
值是否为true@Size
约定字符串长度,其中参数min
表示约定最短长度,max
为最大长度@Min
规定数值型属性的最小值@Max
规定数值型属性的最大值@Email
字符串必须为邮箱格式
每个校验都可以累加,一旦一个类中有校验失败,就会记录其中校验错误。每个校验都有message
参数,可以自定义校验失败时返回的内容。
建议大多数时候使用@NotEmpty注解代替@NotBlank和@NotNull,不过校验数值类型时,还是需要使用@NotNull。
还是接着上面例子,我们将上述User
类修改如下:
packagecom.example.validationtest.dataobject; importlombok.Getter; importlombok.NoArgsConstructor; importlombok.Setter; importjavax.validation.constraints.*; importjava.io.Serializable; /*** 用户类*/publicclassUserimplementsSerializable { /*** 主键id*/privateIntegerid; /*** 用户名*/message="用户名不能为空!") (privateStringusername; /*** 密码*/min=8, message="密码长度不能小于8!") (message="密码不能为空!") (privateStringpassword; /*** 邮箱*/message="邮箱格式错误!") (privateStringemail; /*** 年龄*/value=18, message="年龄不能小于18!") (value=150, message="年龄不能小于150!") (privateintage; }
可以看看字段上的注解的使用,一般设定参数message
自定义错误消息,还有value
参数规定相应校验值等等。
有了这些校验注解,我们就不用再在Service
里面写if
去进行判断了!
4,编写Controller
类并设定传入的User
对象需要校验
现写一简易的Controller
方法,接收POST请求传入User
实例,传入时指定其需要校验,如果校验失败返回校验信息,成功则调用Service
层添加用户(这里省略Service
代码)代码如下:
"/add") (publicStringadd(Useruser, BindingResulterrors) { if (errors.hasErrors()) { returnerrors.getFieldError().getDefaultMessage(); } userService.add(user); return"添加成功!"; }
注意这个Controller方法的参数,传入的User
对象前面打了@Valid
注解,旨在告诉Controller这个需要校验,且比起平常这里多了个参数为BindingResult
对象,这个对象用于存放校验错误信息。一般通过BindingResult
对象的hasErrors
方法判断是否有错误,如果有错误,通过errors.getFieldError().getDefaultMessage()
获取错误信息(上文errors
就是我们的BindingResult
对象)。例如我现在使用Postman工具发送请求数据体如下:
结果:
可见使用Validation
的注解可以轻松实现校验,而不再需要我们逐个手写逻辑。
5,优化校验规则-分组校验
同样是用户对象,我们可能在不同情景下,有着不同的校验规则。例如:
- 用户id通常是自增主键,因此注册用户是传入的用户id字段可以为空,但是用户名密码等等不能为空
- 然而,更新/修改用户信息时是根据用户id找到用户的,这时传入的用户id又不能为空了,但是用户名密码可以为空(一般来说,前端传入用户对象修改用户信息时,空的字段代表这个字段不需要修改保持原值,这时后端将空的字段用原来的值填上并写进数据库即可)
对于不同的校验规则,我们可以为其分别设定校验规则,并在不同的API使用不同的校验规则。
校验规则的参数要填的是类,因此我们用空的接口表示校验规则(空接口可以理解为不同校验规则组的名字),这里我创建param
软件包,在里面建立类ValidationRules
,并在类中写两个空接口分别表示用户注册、修改的校验规则:
packagecom.example.validationtest.param; /*** 校验规则类*/publicclassValidationRules { /*** 注册(添加)用户规则*/publicinterfaceUserAdd { } /*** 更新(修改)用户规则*/publicinterfaceUserUpdate { } }
然后,我们再修改User
类如下:
packagecom.example.validationtest.dataobject; importcom.example.validationtest.param.ValidationRules; importlombok.Getter; importlombok.NoArgsConstructor; importlombok.Setter; importjavax.validation.constraints.*; importjava.io.Serializable; /*** 用户类*/publicclassUserimplementsSerializable { /*** 主键id,将其设定分组为ValidationRules.UserUpdate,表示用户信息修改时校验该规则*/groups=ValidationRules.UserUpdate.class, message="用户id不能为空!") (privateIntegerid; /*** 用户名,将其设定分组为ValidationRules.UserAdd,表示添加用户时校验该规则*/groups=ValidationRules.UserAdd.class, message="用户名不能为空!") (privateStringusername; /*** 密码,将长度校验规则同时加入到ValidationRules.UserAdd和ValidationRules.UserUpdate组,表示添加用户和用户信息修改时都要校验这个规则,空值校验只有添加时校验*/groups= {ValidationRules.UserAdd.class, ValidationRules.UserUpdate.class}, min=8, message="密码长度不能小于8!") (groups=ValidationRules.UserAdd.class, message="密码不能为空!") (privateStringpassword; // 下面都是一回事/*** 邮箱*/groups= {ValidationRules.UserAdd.class, ValidationRules.UserUpdate.class}, message="邮箱格式错误!") (privateStringemail; /*** 年龄*/groups= {ValidationRules.UserAdd.class, ValidationRules.UserUpdate.class}, value=18, message="年龄不能小于18!") (groups= {ValidationRules.UserAdd.class, ValidationRules.UserUpdate.class}, value=150, message="年龄不能小于150!") (privateintage; }
可见,我们在每个校验注释中加上了groups
参数,这个参数就是用于指定每个校验规则的分组。这个参数值必须是类class
类型,用我们创建的空接口即可,就代表给这个规则指定分组在哪个类。一个校验规则不仅仅可以只给它指定一个分组,也可以指定多个分组(参数值写成数组)。
然后现在编写Controller
类,给不同的接口应用不同的规则:
packagecom.example.validationtest.api; importcom.example.validationtest.dataobject.User; importcom.example.validationtest.param.ValidationRules; importorg.springframework.validation.BindingResult; importorg.springframework.validation.annotation.Validated; importorg.springframework.web.bind.annotation.PostMapping; importorg.springframework.web.bind.annotation.RequestBody; importorg.springframework.web.bind.annotation.RequestMapping; importorg.springframework.web.bind.annotation.RestController; /*** 测试校验(这里不做实际的用户添加删除工作,只是测试validation校验)*/"/api/user") (publicclassUserAPI { /*** 模拟添加用户,这里设置校验规则为ValidationRules.UserAdd*/"/add") (publicStringadd( (ValidationRules.UserAdd.class) Useruser, BindingResulterrors) { if (errors.hasErrors()) { returnerrors.getFieldError().getDefaultMessage(); } return"添加成功!"; } /*** 模拟修改用户,这里设置校验规则为ValidationRules.UserUpdate*/"/update") (publicStringupdate( (ValidationRules.UserUpdate.class) Useruser, BindingResulterrors) { if (errors.hasErrors()) { returnerrors.getFieldError().getDefaultMessage(); } return"修改成功!"; } }
测试添加接口:
同样拿这个数据测试修改接口:
这里在Controller
中,我们这里换用了@Validated
注解实现指定分组校验规则进行校验,其中参数值也是代表分组的类(也可以写多个,用数组表示),上述add
接口指定了@Validated
参数为ValidationRules.UserAdd.class
,那么这个接口只会去校验用户类中,设定了groups
参数值包含ValidationRules.UserAdd.class
的规则。
可见分组校验是一个非常好用方便的功能。