实际业务中,我们是离不开数据的校验的。比如注册用户,用户名和密码是不能为空的。
今天的分享,我用一个简单的使用 Spring Boot+MyBatis 程序添加用户为例,来进行讲解。
1,平常我们进行数据校验
例如这个添加用户的查询,我们要将用户类User
的实例添加进数据库,代码如下:
首先是User
类:
package com.example.validationtest.dataobject;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
/**
* 用户类
*/
@Setter
@Getter
@NoArgsConstructor
public class User implements Serializable {
/**
* 主键id
*/
private Integer id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 邮箱
*/
private String email;
/**
* 年龄
*/
private int age;
}
我们会在Service
层里面调用dao
来把数据添加进数据库,不过我们会在Service
类里面写很多校验字段的东西,例如这个添加用户的方法片段:
@Autowired
private UserDAO userDAO;
@Override
public String add(User user) {
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
类修改如下:
package com.example.validationtest.dataobject;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.validation.constraints.*;
import java.io.Serializable;
/**
* 用户类
*/
@Setter
@Getter
@NoArgsConstructor
public class User implements Serializable {
/**
* 主键id
*/
private Integer id;
/**
* 用户名
*/
@NotEmpty(message = "用户名不能为空!")
private String username;
/**
* 密码
*/
@Size(min = 8, message = "密码长度不能小于8!")
@NotEmpty(message = "密码不能为空!")
private String password;
/**
* 邮箱
*/
@Email(message = "邮箱格式错误!")
private String email;
/**
* 年龄
*/
@Min(value = 18, message = "年龄不能小于18!")
@Max(value = 150, message = "年龄不能小于150!")
private int age;
}
可以看看字段上的注解的使用,一般设定参数message
自定义错误消息,还有value
参数规定相应校验值等等。
有了这些校验注解,我们就不用再在Service
里面写if
去进行判断了!
4,编写Controller
类并设定传入的User
对象需要校验
现写一简易的Controller
方法,接收POST请求传入User
实例,传入时指定其需要校验,如果校验失败返回校验信息,成功则调用Service
层添加用户(这里省略Service
代码)代码如下:
@PostMapping("/add")
public String add(@RequestBody @Valid User user, BindingResult errors) {
if (errors.hasErrors()) {
return errors.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
,并在类中写两个空接口分别表示用户注册、修改的校验规则:
package com.example.validationtest.param;
/**
* 校验规则类
*/
public class ValidationRules {
/**
* 注册(添加)用户规则
*/
public interface UserAdd {
}
/**
* 更新(修改)用户规则
*/
public interface UserUpdate {
}
}
然后,我们再修改User
类如下:
package com.example.validationtest.dataobject;
import com.example.validationtest.param.ValidationRules;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.validation.constraints.*;
import java.io.Serializable;
/**
* 用户类
*/
@Setter
@Getter
@NoArgsConstructor
public class User implements Serializable {
/**
* 主键id,将其设定分组为ValidationRules.UserUpdate,表示用户信息修改时校验该规则
*/
@NotNull(groups = ValidationRules.UserUpdate.class, message = "用户id不能为空!")
private Integer id;
/**
* 用户名,将其设定分组为ValidationRules.UserAdd,表示添加用户时校验该规则
*/
@NotEmpty(groups = ValidationRules.UserAdd.class, message = "用户名不能为空!")
private String username;
/**
* 密码,将长度校验规则同时加入到ValidationRules.UserAdd和ValidationRules.UserUpdate组,表示添加用户和用户信息修改时都要校验这个规则,空值校验只有添加时校验
*/
@Size(groups = {ValidationRules.UserAdd.class, ValidationRules.UserUpdate.class}, min = 8, message = "密码长度不能小于8!")
@NotEmpty(groups = ValidationRules.UserAdd.class, message = "密码不能为空!")
private String password;
// 下面都是一回事
/**
* 邮箱
*/
@Email(groups = {ValidationRules.UserAdd.class, ValidationRules.UserUpdate.class}, message = "邮箱格式错误!")
private String email;
/**
* 年龄
*/
@Min(groups = {ValidationRules.UserAdd.class, ValidationRules.UserUpdate.class}, value = 18, message = "年龄不能小于18!")
@Max(groups = {ValidationRules.UserAdd.class, ValidationRules.UserUpdate.class}, value = 150, message = "年龄不能小于150!")
private int age;
}
可见,我们在每个校验注释中加上了groups
参数,这个参数就是用于指定每个校验规则的分组。这个参数值必须是类class
类型,用我们创建的空接口即可,就代表给这个规则指定分组在哪个类。一个校验规则不仅仅可以只给它指定一个分组,也可以指定多个分组(参数值写成数组)。
然后现在编写Controller
类,给不同的接口应用不同的规则:
package com.example.validationtest.api;
import com.example.validationtest.dataobject.User;
import com.example.validationtest.param.ValidationRules;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试校验(这里不做实际的用户添加删除工作,只是测试validation校验)
*/
@RestController
@RequestMapping("/api/user")
public class UserAPI {
/**
* 模拟添加用户,这里设置校验规则为ValidationRules.UserAdd
*/
@PostMapping("/add")
public String add(@RequestBody @Validated(ValidationRules.UserAdd.class) User user, BindingResult errors) {
if (errors.hasErrors()) {
return errors.getFieldError().getDefaultMessage();
}
return "添加成功!";
}
/**
* 模拟修改用户,这里设置校验规则为ValidationRules.UserUpdate
*/
@PostMapping("/update")
public String update(@RequestBody @Validated(ValidationRules.UserUpdate.class) User user, BindingResult errors) {
if (errors.hasErrors()) {
return errors.getFieldError().getDefaultMessage();
}
return "修改成功!";
}
}
测试添加接口:
同样拿这个数据测试修改接口:
这里在Controller
中,我们这里换用了@Validated
注解实现指定分组校验规则进行校验,其中参数值也是代表分组的类(也可以写多个,用数组表示),上述add
接口指定了@Validated
参数为ValidationRules.UserAdd.class
,那么这个接口只会去校验用户类中,设定了groups
参数值包含ValidationRules.UserAdd.class
的规则。
可见分组校验是一个非常好用方便的功能。