从零搭建 Spring Boot 后端项目(十)
简介
在 RESTful 的接口传参时,我们不能信任任何用户输入,所以开发时要进行数据校验。例如经常要写判断字段是否为空,长度限制等,这些代码繁琐,冗长,还容易出错,这里我们使用 Hibernate-Validator 来解决数据校验问题,会使问题的解决方式优雅很多。那么什么是Hibernate-Validator呢,一开始Java规定了一套关于验证器的接口,即Bean Validation(JSR 303 和 JSR 349)。Bean Validation并不是一项技术而是一种规范,需要对其实现。这里hibernate团队就提供了参考实现,即是Hibernate-Validator
常用注解
@AssertFalse | Boolean,boolean | 验证注解的元素值是false |
---|---|---|
@AssertTrue | Boolean,boolean | 验证注解的元素值是true |
@NotNull | 任意类型 | 验证注解的元素值不是null |
@Null | 任意类型 | 验证注解的元素值是null |
@Min(value=值) | BigDecimal,BigInteger,byte,short,int,long等任何Number子类型 | 验证注解的元素值大于等于@Min指定的value值 |
@Max(value=值) | BigDecimal,BigInteger,byte,short,int,long等任何Number子类型 | 验证注解的元素值小于等于@Max指定的value值 |
@DecimalMin(value=值) | BigDecimal,BigInteger,byte,short,int,long等任何Number子类型 | 验证注解的元素值大于等于@DecimalMin指定的value值 |
@DecimalMax(value=值) | BigDecimal,BigInteger,byte,short,int,long等任何Number子类型 | 验证注解的元素值小于等于@DecimalMax指定的value值 |
@Digits(integer=整数位数,fraction=小数位数) | BigDecimal,BigInteger,byte,short,int,long等任何Number子类型 | 验证注解的元素值的整数位数和小数位数上限 |
@Size(min=下限,max=上限) | 字符串,Collection. Map,数组等 | 验证注解的元素值的在min和max (包含)指定区间之内,如字符长度、集合大小 |
@Past | java.util.Date,java.util.Calendar,Joda Time类库的日期类型 | 验证注解的元素值(日期类型)比当前时间早 |
@Future | java.util.Date,java.util.Calendar,Joda Time类库的日期类型 | 验证注解的元素值(日期类型)比当前时间晚 |
@NotBlank | CharSequence子类型 | 验证注解的元素值不为空(不为null、 去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首位空格 |
@Length(min=下限,max=上限) | CharSequence子类型 | 验证注解的元素值长度在min和max区间内 |
@NotEmpty | CharSequence子类型,Collection,Map, 数组 | 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
@Range(min=最小值,max=最大值) | BigDecimal,Biglnteger,CharSequence,byte,short,int,long等原子类型和包装类型 | 验证注解的元素值在最小值和最大值之间 |
@Email(regexp=正则表达式,flag=标志的模式) | CharSequence子类型(如String) | 验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式 |
@Pattern(regexp=正则表达式,flag=标志的模式) | String,任何CharSequence的子类型 | 验证注解的元素值与指定的正则表达式匹配 |
@Valid | 任何非原子类型 | 指定递归验证关联的对象,如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证 |
注:@NotNull,@NotBlank与 @NotEmpty 的用法区别: int 、long基础类型,和Integer、Long等包装类型判断非空时使用 @NotNull,String 类型字符串一般用 @NotBlank,而集合类一般用 @NotEmpty。但具体问题要具体分析,比如为String时允许为空字符串””,不允许为null时,需要用**@NotNull**
除了这些注解,我们还可以自定义校验注解,以应对更多的业务场景。需自定义一个注解与实现ConstraintValidator接口,这里不再过多拓展,有需要自行查找即可
VO、DTO与DO
- DO(Data Object):数据对象,类里的每一个字段,与数据库相对应,即实体类
- DTO(Data Transfer Object):数据传输对象,展示层与服务层之间的数据传输对象
- VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来
大多数情况下DTO和VO的属性值,基本是一致的,所以很多时候后端在实现层面上,就没有写VO。但是在设计层面,两者有着本质的区别:DTO代表服务层需要接收和返回的数据,而VO代表展示层需要显示的数据。例如:0表示男,1表示女,在DTO层就是0和1,但是VO层就可能是0和1,男和女,帅哥和美女,靓仔和靓妹。这里我们用的是 RESTful 风格写的API 相当于只传入或传出DTO,前后端分离,即等于前端帮我们把DTO转成了相应的VO。如果是 MVC 模式,视图与数据相绑定,这时就需要我们再加一层VO了。再比如某个框架(如Flex)提供自动把POJO转换为UI中某些Field时,可以考虑在实现层面定义出VO。总结:设计层面一定要有VO的情况,实际后端服务层实现用不用VO层视情况而定
步骤
spring boot 2.2 版本以前的依赖spring-boot-starter-web中包含了Hibernate-Validator依赖,但spring boot 2.2以后就不包含了,这里要手动添加到pom.xml
<!-- hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Final</version>
</dependency>
这里我们模拟一次正常的接口请求,然后在此过程中,对传入的参数进行数据校验,首先还是建一张数据表
create table employee
(
id int not NULL AUTO_INCREMENT,
name varchar(255) not null,
gender int not null,
date_of_birth datetime null,
phone varchar(255) null,
email varchar(255) null,
dept varchar(255) null,
constraint goods_pk
primary key (id)
);
INSERT INTO employee (name, gender, date_of_birth, phone, email, dept) VALUES ('nail', 0, '1988-10-01', '18945174678', 'nail@163.com', 'Development');
INSERT INTO employee (name, gender, date_of_birth, phone, email, dept) VALUES ('bob', 0, '1990-05-01 00:00:00', '18647815474', 'bob@163.com', 'Test');
- 在数据表上方右键依次按如下步骤开始操作,我们开始生成代码
- 在Package下填写
com.example.backend_template
,然后选择要生成的六个文件,最后点击OK键,以生成六个文件,如果想看更详细的步骤,可以看这篇文章代码自动生成 - 运行项目,用 Postman 访问 http://localhost:8080/employee/selectOne?id=1 ,如出现如下数据,则说明自动生成代码成功,才能接着做其它的步骤
接下来我们在自动生成的代码基础上,增加新的接口,这里我们首先新增一个xxxDTO,在此xxxDTO上做数据校验,在com.example.backend_template.dto新建两个文件夹一个request,一个response,并在request文件夹下新增EmployeeDTO.java类,这里自己写下get和set方法,太多了,这就不写了
package com.example.backend_template.dto.request;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.*;
import java.util.Date;
/**
* @ClassName EmployeeDTO
* @Description
* @Author L
* @Date Create by 2020/7/16
*/
public class EmployeeDTO {
@NotBlank(message = "name must not be null!")
private String name;
@NotNull(message = "gender must not be null!")
@Range(min = 0, max = 1, message = "gender must be one of the following 0 or 1!")
private Integer gender;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@NotNull(message = "date_of_birth must not be null!")
@Past(message = "date_of_birth has to be past time!")
@JsonProperty(value = "date_of_birth")
private Date dateOfBirth;
private String phone;
@Email(message = "email format is incorrect! ")
private String email;
@NotBlank(message = "dept must not be null!")
@Pattern(regexp = "Test|Development|Administration", message = "dept must be one of the following Test、Development、Administration!")
private String dept;
@NotNull(message = "salary must not be null!")
private Integer salary;
//get and set...
}
注:我们假设页面会传以上信息,尽管我们可能不会全存到employee,因为正常开发情况下,它可能传到别的表,或做别的处理
在com.example.backend_template.service.EmployeeService.java类下新加以下接口方法,注意导入相关的类,之后也是
/**
* 创建employee相关信息,包括基本信息和工资信息
*
* @param dto
* @return
*/
EmployeeDTO createEmployeeInfo(EmployeeDTO dto);
为了之后方便,先在com.example.backend_template.entity.Employee.java类下新增两个构造方法
public Employee() {
}
public Employee(String name, Integer gender, Date dateOfBirth, String phone, String email, String dept) {
this.id = id;
this.name = name;
this.gender = gender;
this.dateOfBirth = dateOfBirth;
this.phone = phone;
this.email = email;
this.dept = dept;
}
在com.example.backend_template.service.EmployeeServiceImpl.java类下,新增以下实现方法
@Override
public EmployeeDTO createEmployeeInfo(EmployeeDTO dto) {
Employee employee = new Employee(dto.getName(), dto.getGender(),
dto.getDateOfBirth(), dto.getPhone(), dto.getEmail(), dto.getDept());
employeeDao.insert(employee);
//假设薪水是做别的存储处理
dto.getSalary();
return dto;
}
在com.example.backend_template.controller.EmployeeController.java类下,新增以下接口, 并将返回结果统一成ResultData
数据结构 /**
* 创建employee相关信息,包括基本信息和工资信息
* @param dto
* @return
*/
@PostMapping("")
public ResultData<Object> createEmployeeInfo(@RequestBody @Valid EmployeeDTO dto){
EmployeeDTO employeeDTO = employeeService.createEmployeeInfo(dto);
return ResultUtils.success(employeeDTO);
}
启动项目,用Postman对 http://localhost:8080/employee 发起POST请求,参数如下
{
"name":"Tom",
"gender":0,
"date_of_birth":"1999-10-01",
"phone":"13615498742",
"email":"tom@163.com",
"dept":"Development",
"salary":2000
}
成功后,结果如下图
此时传数据的格式不对,就会报参数校验的相关错误,错误很多,可自行尝试,例如,这里使name为空会报如下错误
{
"code": 400,
"msg": "Method Argument Not Valid!",
"data": "Validation failed for argument [0] in public com.example.backend_template.utils.ResultData<java.lang.Object> com.example.backend_template.controller.EmployeeController.createEmployeeInfo(com.example.backend_template.dto.request.EmployeeDTO): [Field error in object 'employeeDTO' on field 'name': rejected value [null]; codes [NotBlank.employeeDTO.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [employeeDTO.name,name]; arguments []; default message [name]]; default message [name must not be null!]] "
}
结果图如下
测试完,知道怎么用后,就可以把这些多余的类删除了
项目地址
项目介绍:从零搭建 Spring Boot 后端项目
代码地址:https://github.com/xiaoxiamo/backend-template
下一篇
十一、全局日志处理
还没有评论,来说两句吧...