JSR303参数校验配置
一、环境
- jdk1.8
- idea2018.2.2
- maven3.5.3
- mybatsi-plus3.4.1
- mysql5.7
二、项目配置
- pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.yxl</groupId>
<artifactId>yxl</artifactId>
<packaging>pom</packaging>
<version>1.0.0</version>
<modules>
<module>yxl_project01</module>
</modules>
<name>yxl</name>
<description>公共项目</description>
<properties>
<druid-version>1.2.6</druid-version>
<mybatis-plus-version>3.4.1</mybatis-plus-version>
<mysql-connector-version>8.0.23</mysql-connector-version>
<lombok-version>1.18.18</lombok-version>
<hutool-version>5.6.5</hutool-version>
<httpcore-version>4.4.12</httpcore-version>
<validation-version>2.0.1.Final</validation-version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.6</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok-version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool-version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>${httpcore-version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
注:
spirngboot升级到2.3之后,hibernate-validator消失,需要自己手动加入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
或者是自己手动引入依赖
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
</dependency>
- 统一返回结果
package com.yxl.common.util;
import org.apache.http.HttpStatus;
import java.util.HashMap;
import java.util.Map;
/**
* @Classname R
* @Description TODO
* @Date 2021-12-20 17:19
* @Created by yxl
*/
public class R extends HashMap<String,Object> {
private static final long serialVersionUID = 1L;
public R(){
put("code",0);
put("msg","success");
}
public static R error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
}
public static R error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}
public static R ok() {
return new R();
}
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}
- controller
package com.yxl.project01.controller;
import com.yxl.common.util.R;
import com.yxl.project01.entity.User;
import com.yxl.project01.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @Classname UserController
* @Description TODO
* @Date 2021-12-20 17:14
* @Created by yxl
*/
@RestController
@RequestMapping("/user")
public class UserController {
}
- dao
package com.yxl.project01.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yxl.project01.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* @Classname UserMapper
* @Description TODO
* @Date 2021-12-20 17:10
* @Created by yxl
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
- entity
package com.yxl.project01.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
/**
* @Classname User
* @Description TODO
* @Date 2021-12-20 17:07
* @Created by yxl
*/
@Data
@TableName("tb_user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
private Date birthday;
private String icon;
private Integer status;
private String description;
}
- service
package com.yxl.project01.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yxl.project01.entity.User;
/**
* @Classname UserService
* @Description TODO
* @Date 2021-12-20 17:11
* @Created by yxl
*/
public interface UserService extends IService<User> {
}
- serviceImpl
package com.yxl.project01.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yxl.project01.dao.UserMapper;
import com.yxl.project01.entity.User;
import com.yxl.project01.service.UserService;
import org.springframework.stereotype.Service;
/**
* @Classname UserServiceImpl
* @Description TODO
* @Date 2021-12-20 17:12
* @Created by yxl
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
- 启动类
package com.yxl.project01;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @Classname yxlProject01Application
* @Description TODO
* @Date 2021-12-20 17:03
* @Created by yxl
*/
@SpringBootApplication
@MapperScan("com.yxl.project01.dao")
public class yxlProject01Application {
public static void main(String[] args) {
SpringApplication.run(yxlProject01Application.class,args);
}
}
三、JSR303参数校验配置
1、在UserController中新增save接口用来添加用户
/**
* JSR303配置案例
*/
@PostMapping("/save")
public R save(@RequestBody User user){
userService.save(user);
return R.ok();
}
2、使用Postman对接口进行测试
测试数据
{
"name": "张三",
"age": 20,
"birthday": "1995-03-02",
"icon": "http://www.baidu.com",
"status": "0",
"description": "描述"
}
测试结果如下:
从上边图可以看出来执行成功了,也新增到数据库了。
那么我们现在把所有新增的字段都置空,看新增的时候会怎么样?
{
"name": "",
"age": "",
"birthday": "",
"icon": "",
"status": "",
"description": ""
}
从执行结果中可以看出,新增依旧是执行成功的,但是数据库加入的都是空数据,这样肯定是不行的,那么我们怎么样能限制到新增的时候给每个属性加限制呢?就比如说名称不能为空,url必须是合法的url地址等等。这时候我们就用到了JSR303参数校验注解了。演示如下:
1、在User.Class中新增校验注解如下:
package com.yxl.project01.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* @Classname User
* @Description TODO
* @Date 2021-12-20 17:07
* @Created by yxl
*/
@Data
@TableName("tb_user")
public class User {
@TableId
private Long id;
@NotBlank(message = "用户名不能为空")
private String name;
@NotNull(message = "年龄不能为空")
private Integer age;
@NotNull(message = "出生日期不能为空")
private Date birthday;
@NotBlank(message = "头像不能为空")
private String icon;
private Integer status;
private String description;
}
再此对@NotBlank、@NotNull、@NotEmpty三个注解做一个解释
@NotBlank:只用于String,不能为null且trim()之后size>0
@NotNull:用在基本数据类型上面。不能为null,但可以为empty,没有Size的约束
@NotEmpty:用在集合类上面。加了@NotEmpty的String类、Collection、Map、数组,是不能为null或者长度为0的(String Collection Map的isEmpty()方法)
2、在Controller中使用校验注解
/**
* JSR303配置案例
*/
@PostMapping("/save")
public R save(@Valid @RequestBody User user){
userService.save(user);
return R.ok();
}
3、重启服务器,再次使用空数值测试结果如下:
{
"timestamp": "2021-12-22T07:19:17.464+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotNull.user.age",
"NotNull.age",
"NotNull.java.lang.Integer",
"NotNull"
],
"arguments": [
{
"codes": [
"user.age",
"age"
],
"arguments": null,
"defaultMessage": "age",
"code": "age"
}
],
"defaultMessage": "年龄不能为空",
"objectName": "user",
"field": "age",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
},
{
"codes": [
"NotNull.user.birthday",
"NotNull.birthday",
"NotNull.java.util.Date",
"NotNull"
],
"arguments": [
{
"codes": [
"user.birthday",
"birthday"
],
"arguments": null,
"defaultMessage": "birthday",
"code": "birthday"
}
],
"defaultMessage": "出生日期不能为空",
"objectName": "user",
"field": "birthday",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
},
{
"codes": [
"NotBlank.user.icon",
"NotBlank.icon",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"user.icon",
"icon"
],
"arguments": null,
"defaultMessage": "icon",
"code": "icon"
}
],
"defaultMessage": "头像不能为空",
"objectName": "user",
"field": "icon",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
},
{
"codes": [
"NotBlank.user.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"user.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "用户名不能为空",
"objectName": "user",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='user'. Error count: 4",
"path": "/user/save"
}
从结果看参数校验注解已生效。
但是回过头来香,如果是前后端分离项目的话,返回给前端这种格式是不行的,那么我们就可以利用全局异常处理,统一来返回结果给前端了。
4、在全局异常处理类中增加异常处理方法专门对MethodArgumentNotValidException.Class异常类进行处理,如下:
package com.yxl.project01.exception;
import com.yxl.common.exception.BizCodeEnume;
import com.yxl.common.util.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
import java.util.Map;
/**
* @Classname GlobalExceptionHandler
* @Description TODO
* @Date 2021-12-22 11:13
* @Created by yxl
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public R arithmeticExceptionHandler(ArithmeticException e){
return R.error(BizCodeEnume.ARITHMETIC_EXCEPTION.getCode(),BizCodeEnume.ARITHMETIC_EXCEPTION.getMsg());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public R methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型:{}",e.getMessage().getClass());
BindingResult result = e.getBindingResult();
Map<String,String> map = new HashMap<>();
result.getFieldErrors().forEach(fieldError -> {
map.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",map);
}
}
其中BizCodeEnume枚举类如下:
package com.yxl.common.exception;
/**
* @Classname BizCodeEnume
* @Description TODO
* @Date 2021-12-22 11:29
* @Created by yxl
*/
public enum BizCodeEnume {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验失败"),
ARITHMETIC_EXCEPTION(10001,"算术异常"),;
private int code;
private String msg;
private BizCodeEnume(int code, String msg){
this.code = code;
this.msg = msg;
}
public int getCode(){
return code;
}
public String getMsg(){
return msg;
}
}
新增全局异常处理后我们再测试一下,结果如下:
{
"msg": "参数格式校验失败",
"code": 10001,
"data": {
"birthday": "出生日期不能为空",
"icon": "头像不能为空",
"name": "用户名不能为空",
"age": "年龄不能为空"
}
}
这就达到了我们想要的结果。
三、参数校验分组功能
参数校验分组功能主要是针对哪些方法使用哪些参数校验的,比如:新增用户前端不需要传id等字段,修改的时候前端必须传id,新增和修改都必须要传name等。这种场景的话,我们就需要用到分组功能了。
1、我们在UserController.class中新增修改接口,如下:
/**
* 参数校验分组功能
*/
@PostMapping("/update")
public R update(@RequestBody User user){
userService.updateById(user);
return R.ok();
}
2、配置User实体类
package com.yxl.project01.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.yxl.common.valid.SaveGroup;
import com.yxl.common.valid.UpdateGroup;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import java.util.Date;
/**
* @Classname User
* @Description TODO
* @Date 2021-12-20 17:07
* @Created by yxl
*/
@Data
@TableName("tb_user")
public class User {
@TableId
@NotNull(message = "修改需要传递id",groups = {UpdateGroup.class})
@Null(message = "新增不需要传递id",groups = {SaveGroup.class})
private Long id;
@NotBlank(message = "用户名不能为空",groups = {SaveGroup.class,UpdateGroup.class})
private String name;
@NotNull(message = "年龄不能为空")
private Integer age;
@NotNull(message = "出生日期不能为空")
private Date birthday;
@NotBlank(message = "头像不能为空")
private String icon;
private Integer status;
private String description;
}
其中SaveGroup.class和UpdateGroup.class为groups分组需要传递的标识符,即一个空的接口。如下:
package com.yxl.common.valid;
/**
* @Classname SaveGroup
* @Description TODO
* @Date 2021-12-22 15:42
* @Created by yxl
*/
public interface SaveGroup {
}
package com.yxl.common.valid;
/**
* @Classname UpdateGroup
* @Description TODO
* @Date 2021-12-22 15:42
* @Created by yxl
*/
public interface UpdateGroup {
}
3、在UserController中save接口和update接口增加注解
/**
* JSR303配置案例
*/
@PostMapping("/save")
public R save(@Validated({SaveGroup.class}) @RequestBody User user){
userService.save(user);
return R.ok();
}
/**
* 参数校验分组功能
*/
@PostMapping("/update")
public R update(@Validated({UpdateGroup.class}) @RequestBody User user){
userService.updateById(user);
return R.ok();
}
4、执行修改接口,提示修改需要传递id。如下:
我们传一个id进去,修改一条数据试试:
数据库数据已更改
5、执行新增接口,提示新增不需要传id。如下:
我们把Id去掉在测试
数据库已新增成功