本项目使用环境
开发工具:Intellij IDEA 2017.1.3
springboot: 2.2.6
jdk:1.8.0_74
maven:3.3.9
备注:
我们平时写代码时对于某个实体需要做判空校验或则一些其他校验,平时我们可能会在代码中一行行判断;此时我们经常会if(user.getUserName==null)…若判断字段较多的话可能就会比较麻烦,并且造成了代码的不简洁。我们可以借助于hibernate validate解决。我也是第一次用,有什么建议感谢留言(废话不多说,上“才艺”)
1、首先pom中引入所需要的包
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
2、创建user实体类
package com.example.demo.dto;
import com.example.demo.service.AddGroup;
import com.example.demo.service.ListGtOne;
import com.example.demo.service.SaveGroup;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.List;
@Data
@GroupSequence({AddGroup.class,SaveGroup.class,User.class})
public class User implements Serializable {
/**
* id
*/
@NotNull(message = "用户id不能为空", groups = SaveGroup.class)
private Integer id;
/**
* 姓名
*/
@NotBlank(message = "用户名称不能为空", groups = {AddGroup.class, SaveGroup.class})
private String name;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 18)
private String password;
/**
* 年龄
*/
@Min(1)
@Max(100)
private Integer age;
/**
* 邮箱
*/
@Email(regexp = "^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\\.[a-zA-Z0-9]{2,6}$", message = "邮件格式错误")
private String email;
/**
* 手机号
*/
@NotBlank(message = "手机号不能为空")
@Size(min = 11, max = 11)
private String mobile;
/**
* 学习科目
*/
@ListGtOne(message = "用户学习科目至少两科", groups = {SaveGroup.class})
@Valid
List<Subject> subjectList;
}
上面@NotBlank、@Size等注解就是表示当前字段满足要求,具体注释可以网上查找。
3、ValidateUtil为校验方法
这个可以自由发挥,网上一些比较优秀的博主定义了一个validateUtil类来处理,我这里直接写到了公共类里面,其他需要校验的类直接继承了一下!
package com.example.demo.controller;
import com.example.demo.dto.CommonResult;
import com.example.demo.dto.ResultUtil;
import com.example.demo.dto.User;
import com.example.demo.enumeration.ErrorCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.HibernateValidator;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import java.util.Iterator;
import java.util.Set;
@Slf4j
public abstract class BaseController {
private static javax.validation.Validator validator;
static {
/**
* 使用hibernate的注解来进行验证 failFast true仅仅返回第一条错误信息 false返回所有错误
*/
validator = Validation
.byProvider(HibernateValidator.class)
.configure().failFast(true)
.buildValidatorFactory()
.getValidator();
}
/**
* @param obj 校验的对象
* @param <T>
* @return
*/
public static <T> Set<ConstraintViolation<T>> validate(T obj, Class... groupClasses) {
Set<ConstraintViolation<T>> constraintViolations;
if (groupClasses != null && groupClasses.length > 0) {
constraintViolations = validator.validate(obj, groupClasses);
} else {
constraintViolations = validator.validate(obj);
}
return constraintViolations;
}
/**
* 校验参数是否合法
*
* @param result
* @return
*/
public static CommonResult checkoutParams(Set<ConstraintViolation<User>> result) {
if (result.size() > 0) {
// 抛出检验异常
Iterator<ConstraintViolation<User>> it = result.iterator();
// 错误消息
String message = "";
while (it.hasNext()) {
ConstraintViolation<User> str = it.next();
message = str.getMessage();
}
return ResultUtil.returnError("-1", message);
}
//result小于等于0,表示验证通过
return ResultUtil.returnSuccess(null);
}
}
注意:使用hibernate的注解来进行验证 failFast true仅仅返回第一条错误信息 false返回所有错误(感兴趣的话可以把这里设置成false试试,看看会怎样)
这里需要说明一下(由于日常的业务比较复杂,这里运用了一个自定义注解).
详细说明
如下截图subjectCode,subjectName都不能为空,那上面是否生效呢?经过测试是发现是不生效的,需要加另外个注释@Valid才能生效;科目列表里需要需至少两个,即subjectList.size()>2,这时会发现当前注释无法满足需求,因此我们要自定义一个自己的注释,使之和@NotBlank等起到一样的效果。
subject实体
package com.example.demo.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
@Data
public class Subject implements Serializable {
@NotBlank(message = "科目编码不能为空")
private String subjectCode;
@NotNull(message = "科目名称不能为空")
private String subjectName;
}
自定义注解说明
package com.example.demo.service;
import com.example.demo.service.impl.ListNotEmptyValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
/**
* 自定义注解,验证学科不能少于2科
*/
@Constraint(
validatedBy = {ListNotEmptyValidator.class}
)
public @interface ListGtOne {
/**
* 以下方法仿照@NotBlank注释
*/
String message() default "列表数据为空!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
接下来就是ListGtOne对应的实现,只需要继承ConstraintValidator既可:(既:实现这个接口)
package com.example.demo.service.impl;
import com.example.demo.service.ListGtOne;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Collection;
public class ListNotEmptyValidator implements ConstraintValidator<ListGtOne, Collection> {
public void initialize(ListGtOne listNotEmpty) {
}
public boolean isValid(Collection collection, ConstraintValidatorContext constraintValidatorContext) {
if (collection == null || collection.size() < 2) {
return false;
}
return true;
}
}
这个时候,自定义注解就已经处理好了;接下来就是怎么运用了。如下:(@ListGtOne)
/**
* 学习科目
* @ListGtOne为自定义注解的引用 @ + 自定义的注解(ListGtOne)
*/
@ListGtOne(message = "用户学习科目至少两科")
@Valid
List<Subject> subjectList;
分组校验
由于需求的不同,所以校验的字段也会不同;我们不可能因为某个字段的校验规则不同;去把代母重复化,这样太麻烦了,和我们想要的简洁就有一定的差距了。所以这里我们用到了分组校验!
实现如下:
这里我还是拿user实体类做测试,如上(user实体类中 id,name就用到了分组检验),这里模拟了一个常见的引用场景,新增,和更新(我们都知道,新增的时候我们是不用去校验主键id的;更新则不然,这个时候分组校验就友好的帮助我们解决了这个问题了!)
**关键字:groups **
/**
* id
*/
@NotNull(message = "用户id不能为空", groups = SaveGroup.class)
private Integer id;
/**
* 姓名
*/
@NotBlank(message = "用户名称不能为空", groups = {AddGroup.class, SaveGroup.class})
private String name;
说明一下:name里面 groups = {AddGroup.class, SaveGroup.class },这里指的是新增的时候,更新的时候都需要校验,但是已新增优先于更新
下面介绍一下AddGroup.class,SaveGroup.class;其实这两个就是一个接口并且不需要去实现它,名字可以顺便取;只要能做区分分组就行;当然不写时它会有个默认的分组 Default.class,所有不填写的都会划入这个分组。
如下图:(AddGroup.class, SaveGroup.class)这是两个接口
package com.example.demo.service;
/**
* 用于接口Api验证分组
*/
public interface AddGroup {
}
package com.example.demo.service;
/**
* 用于接口Api验证分组
*/
public interface SaveGroup {
}
说了这么多:下面看看怎么用!
package com.example.demo.controller;
import com.example.demo.dto.CommonResult;
import com.example.demo.dto.ResultUtil;
import com.example.demo.dto.Subject;
import com.example.demo.dto.User;
import com.example.demo.enumeration.ErrorCodeEnum;
import com.example.demo.service.AddGroup;
import com.example.demo.service.SaveGroup;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.ConstraintViolation;
import javax.validation.groups.Default;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@Slf4j
@RestController
@RequestMapping("/hello")
public class HellorController extends BaseController {
/**
*
* @return
*/
@ResponseBody
@RequestMapping(value = "test", method = RequestMethod.GET)
public Object test() {
try {
User user = new User();
user.setId(null);
user.setName("");
user.setPassword("12312312312");
user.setMobile("15120322112");
user.setEmail("871331263@qq.com");
List<Subject> subjectList = new ArrayList<Subject>();
Subject subject = new Subject();
subject.setSubjectCode("A001");
subject.setSubjectName("");
subjectList.add(subject);
user.setSubjectList(subjectList);
//调用ValidateUtil中的方法校验
Set<ConstraintViolation<User>> result = validate(user,Default.class, AddGroup.class,SaveGroup.class);
//校验参数是否合法
CommonResult resultUtil = checkoutParams(result);
log.info("test params:{}", user);
if (!resultUtil.isSuccess()) {
return ResultUtil.returnError(ErrorCodeEnum.ERROR.getCode(), resultUtil.getMessage());
}
return ResultUtil.returnSuccess(user);
} catch (Exception e) {
log.error("test error:{}", e.getMessage());
return ResultUtil.returnError(ErrorCodeEnum.ERROR);
}
}
}
这里要注意一下:validate(user,Default.class, AddGroup.class,SaveGroup.class);user就是我们定义好的实体类,后面的Default、AddGroup、SaveGroup 如果不需要用到分组校验,则不需要写,默认会根据Default来校验!另外这三个分组的顺序是可以调整的,校验时优先顺序会根据自己调整的顺序来进行校验!
以下是返回的实体类和枚举定义:测试的时候用的,可自由发挥!
CommonResult实体类如下:
package com.example.demo.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class CommonResult<T> implements Serializable {
/**
* serialVersionUID:.
*/
private static final long serialVersionUID = -7268040542410707954L;
/**
* 是否成功
*/
private boolean success = false;
/**
* 返回信息
*/
private String message;
/**
* 装在数据
*/
private T data;
/**
* 错误代码
*/
private String code;
/**
* 默认构造器
*/
public CommonResult() {
}
/**
* @param success 是否成功
* @param message 返回的消息
*/
public CommonResult(boolean success, String message) {
this.success = success;
this.message = message;
}
/**
* @param success 是否成功
*/
public CommonResult(boolean success) {
this.success = success;
}
/**
* @param code error code
* @param message success or error messages
*/
public CommonResult(String code, String message) {
this.code = code;
this.message = message;
}
/**
* @param success 是否成功
* @param message 消息
* @param data 数据
*/
public CommonResult(boolean success, String message, T data) {
this.success = success;
this.message = message;
this.data = data;
}
}
ResultUtil实体类如下:
package com.example.demo.dto;
import com.example.demo.enumeration.ErrorCodeEnum;
public class ResultUtil {
/**
* return success
* @param data
* @return
*/
public static <T> CommonResult<T> returnSuccess(T data) {
CommonResult<T> result = new CommonResult();
result.setCode(ErrorCodeEnum.SUCCESS.getCode());
result.setSuccess(true);
result.setData(data);
result.setMessage(ErrorCodeEnum.SUCCESS.getDesc());
return result;
}
/**
* return error
*
* @param code error code
* @param msg error message
* @return
*/
public static CommonResult returnError(String code, String msg) {
CommonResult result = new CommonResult();
result.setCode(code);
result.setData("");
result.setMessage(msg);
return result;
}
/**
* use enum
*
* @param status
* @return
*/
public static CommonResult returnError(ErrorCodeEnum status) {
return returnError(status.getCode(), status.getDesc());
}
}
访问一下就可以看到验证的消息了。
我这里的验证地址是:http://localhost:8888/hello/test
提示的错误消息:{“success”:false,“message”:“用户名称不能为空”,“data”:"",“code”:"-1"}
最后在说明一下:因为我这里在调用的时候,Default.class分组我放到了第一位,所以Default优先于去他的分组,感兴趣的可以重置一下 test方法中user实体类的参数值
,然后在把 且user实体类中和test方法中
@GroupSequence({AddGroup.class,SaveGroup.class,User.class})
//调用ValidateUtil中的方法校验
Set<ConstraintViolation<User>> result = validate(user, Default.class,SaveGroup.class, AddGroup.class);
分组的顺序调整一下,就可以看到不同的效果了!