이번 챕터를 학습하며 작성했던 Item 관련 프로젝트 혹시라도 이해가 잘 되지 않는다면 코드를 import하여 참고하자.
특정 구현체가 아닌 Bean Validation 2.0(JSR-380)이라는 기술 표준으로 여러 검증 애노테이션과 여러 인터페이스의 모음이다. (ex: JPA라는 표준 기술에 구현체로 하이버네이트가 있다.)
이러한 Bean Validation을 구현한 기술중 일반적으로 사용하는 구현체는 하이버네이트 Validator이다.
(이름이 하이버네이트지만 ORM과는 관련없다. )
즉, Bean Validation 를 활용하면 애노테이션 기반으로 우리가 이전에 구현해봤던 각종 구현로직들을 간단하게 적용할 수 있다.
우리가 이전에 사용했던 검증방식은 직접 Validator 인터페이스를 구현한 뒤 InitBinder로 구현한 검증기를 등록해서 사용하는 식이였다.
@Component
public class ItemValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Item.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Item item = (Item) target;
ValidationUtils.rejectIfEmpty(errors, "itemName", "required");
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1_000_000) {
errors.rejectValue("price", "range", new Object[]{1000, 1_000_000}, null);
}
if (item.getQuantity() == null || item.getQuantity() >= 9999) {
errors.rejectValue("quantity", "max", new Object[]{9999}, null);
}
//복합 룰 검증
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
errors.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
}
}
@Controller
@RequiredArgsConstructor
public class ValidationItemControllerV2 {
private final ItemValidator itemValidator;
@InitBinder
public void init(WebDataBinder dataBinder){
dataBinder.addValidators(itemValidator);
}
@PostMapping("/add")
public String addItemV6(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
//검증 실패시 다시 입력 폼으로 이동해야 한다.
if (bindingResult.hasErrors()) {
log.info("errors = {}", bindingResult);
return "validation/v2/addForm";
}
...
}
}
이 방식도 처음보다는 많이 간결해진 코드지만, 이마저도 Bean Validation을 사용하면 훨씬 간단해진다. 다음은 간단하게 위 유효성 검증을 Bean Validation을 적용한 코드이다. 도메인에서 검증이 필요한 필드에 바로 적용을 해준다.
@Data
public class Item {
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1_000_000)
private Integer price;
@NotNull
@Max(9999)
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
짧은 애노테이션 몇 가지로 기존의 작성한 방대한 검증 로직들이 대부분 대치된다.