緊接上回分解,沒有看到第一章的童鞋可以點擊此處回放上一章節內容
實體類
@Data
@AgeSalaryType
public class Student {
private Long id;
@NotBlank(message = "姓名不能為空")
private String name;
@NotNull
@Min(value = 5, message = "年齡不能低於5歲")
private int age;
@IdentifyFieldValue(enumClass = OrderType.class)
private String orderType;
@NotNull
@Digits(integer = 10, fraction = 2, message = "請保留小數點后2位")
private BigDecimal salary;
}
業務場景
新增學生信息,只需上面這些校驗即可
更新學生信息,id
為必傳項,需要id
屬性上增加注解@NotNull
如果新建一個實體類不免顯得有些雞肋了,groups
專門解決此類問題
校驗組能夠讓你在驗證的時候選擇應用哪些約束條件. 這樣在某些情況下( 例如向導 ) 就可以對每一步進行校驗的時候, 選取對應這步的那些約束條件進行驗證了. 校驗組是通過可變參數傳遞給
validate
,validateProperty
和validateValue
的.
如果某個約束條件屬於多個組,那么各個組在校驗時候的順序是不可預知的. 如果一個約束條件沒有被指明屬於哪個組,那么它就會被歸類到默認組(javax.validation.groups.Default).
- 新增分組接口
public interface UpdateGroup {
}
public interface AddGroup {
}
- 在實體類上為注解分配組別
@Data
@AgeSalaryType(groups = AddGroup.class)
public class Student {
@NotNull(message = "id主鍵不能為空", groups = UpdateGroup.class)
private Long id;
@NotBlank(message = "姓名不能為空", groups = Default.class)
private String name;
@NotNull
@Min(value = 5, message = "年齡不能低於5歲")
private int age;
@IdentifyFieldValue(enumClass = OrderType.class)
private String orderType;
@NotNull
@Digits(integer = 10, fraction = 2, message = "請保留小數點后2位")
private BigDecimal salary;
}
注:Default.class
是默認組別,無需顯示聲明,我只是標注出來給大家看一下例子
- 測試類
def testStudent() {
Student student = new Student();
student.setAge(20);
student.setSalary(new BigDecimal(50));
Set<ConstraintViolation<Student>> result = validator.validate(student, Default.class);
printfError(result);
Set<ConstraintViolation<Student>> result2 = validator.validate(student, UpdateGroup.class);
printfError(result2);
Set<ConstraintViolation<Student>> result3 = validator.validate(student, AddGroup.class);
printfError(result3);
expect:
true
}
- 測試結果
==================
姓名不能為空
==================
id主鍵不能為空
==================
當年齡大於18歲時,每月薪水不得低於100元
<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
可單獨校驗某個分組,這是單獨分割出來校驗,放在Controller
層接口上表示為:
@PostMapping(value = "all")
public String allTableType(@RequestBody @Validated(Update.class) TableType tableType) {
return JSONObject.toJSONString(tableTypeService.list());
}
關於此種在接口增加校驗的方式,需要綁定BindingResult
獲取錯誤信息,寫一個完整的實例吧
- 就用上面的實體類,下面是接口
@RequestMapping("/add")
public Map<String, Object> addStudent(@Validated({Default.class, AddGroup.class}) Student student, BindingResult bindingResult) {
Map<String, Object> resultMap = new HashMap<>(10);
resultMap.put("success", true);
if (bindingResult.hasErrors()) {
resultMap.put("success", false);
StringBuilder stringBuilder = new StringBuilder();
bindingResult.getAllErrors().stream().forEach(it -> stringBuilder.append(it.getDefaultMessage()));
resultMap.put("message", stringBuilder.toString());
return resultMap;
}
···
return resultMap;
}
- PostMan 反饋信息
接下來再考慮一個問題,不能每個接口都增加BindingResult bindingResult
對象吧,很雞肋的事情,可以在全局異常進行捕獲輸出
@ControllerAdvice
public class ValidateExceptionHandle {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public RestResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
StringBuilder stringBuilder = new StringBuilder("基礎參數驗證失敗:");
if (ex.getBindingResult().getAllErrors().size() > 0) {
for (ObjectError allError : ex.getBindingResult().getAllErrors()) {
stringBuilder.append("[").append(allError.getDefaultMessage()).append("] ");
}
}
return RestResponse.failedMessage(stringBuilder.toString());
}
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public RestResponse resolveConstraintViolationException(ConstraintViolationException ex) {
StringBuilder stringBuilder = new StringBuilder("基礎參數驗證失敗:");
Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
if (!CollectionUtils.isEmpty(constraintViolations)) {
for (ConstraintViolation constraintViolation : constraintViolations) {
stringBuilder.append("[").append(constraintViolation.getMessage()).append("]");
}
}
return RestResponse.failedMessage(stringBuilder.toString());
}
接下來考慮另外一種場景,上述實體類中,如果需要校驗很多組別,按順序校驗,如果前面的某個組別驗證失敗,就不再校驗后面的組別了
@GroupSequence 定義組別之間校驗的順序
- 實體類
@Data
@AgeSalaryType(groups = AddGroup.class)
@GroupSequence({UpdateGroup.class, AddGroup.class, Student.class})
public class Student {
@NotNull(message = "id主鍵不能為空", groups = UpdateGroup.class)
private Long id;
@Size(min = 5, max = 10, message = "姓名長度在5-10")
private String name;
@NotNull
@Min(value = 5, message = "年齡不能低於5歲")
private int age;
@IdentifyFieldValue(enumClass = OrderType.class)
private String orderType;
@NotNull
@Digits(integer = 10, fraction = 2, message = "請保留小數點后2位")
private BigDecimal salary;
}
- 測試類
def testStudent() {
Student student = new Student();
student.setName("你哈")
student.setAge(20);
student.setSalary(new BigDecimal(50));
Set<ConstraintViolation<Student>> result = validator.validate(student);
printfError(result);
expect:
true
}
- 測試結果(UpdateGroup只校驗了Id有錯誤就直接返回了)
id主鍵不能為空
將實體類上組別更換為@GroupSequence({AddGroup.class, UpdateGroup.class, Student.class})
- 測試結果(只校驗了AddGroup,就返回結果)
當年齡大於18歲時,每月薪水不得低於100元
@GroupSequenceProvider 根據對象狀態動態重定義默認分組
public class StudentGsProvider implements DefaultGroupSequenceProvider<Student> {
@Override
public List<Class<?>> getValidationGroups(Student student) {
List<Class<?>> defaultGroupSequence = new ArrayList<>();
defaultGroupSequence.add(Student.class);
if (student != null && student.getAge() < 18) {
defaultGroupSequence.add(AddGroup.class);
}
return defaultGroupSequence;
}
}
可以根據對象條件,將分組信息加入到默認的Default分組里面去,網上有博客用這種方式來解決對象屬性之間依賴的問題,也是可行的
好了,關於Hibernate Validator
的知識點,先介紹這么多,下一節講述如何運用Hibernate Validator
實現Excel全表校驗的邏輯
Java is the best language in the world
最近北京新肺疫情應急響應級別又上升為二級,希望在帝都的朋友都多多注意,提高防范意識,下播...