implementation 'org.springframework.boot:spring-boot-starter-validation'
자동으로 Bean Validator를 인지하고 스프링에 통합한다.
자동으로 LocalValidatorFactoryBean 을 글로벌 Validator로 등록한다. 이 Validator는 @NotNull 같은 애노테이션을 보고 검증을 수행한다. 이렇게 글로벌 Validator가 적용되어 있기 때문에 @Valid ,@Validated 만 적용하면 된다.
검증 오류가 발생하면, FieldError , ObjectError 를 생성해서 BindingResult 에 담아준다.
BeanValidation 메시지 찾는 순서
1. 생성된 메시지 코드 순서대로 messageSource 에서 메시지 찾기
2. 애노테이션의 message 속성 사용 @NotBlank(message = "공백! {0}")
3. 라이브러리가 제공하는 기본 값 사용
@NotBlank(message = "공백! {0}")
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
@NotNull
@Max(9999)
private Integer quantity;
NotBlank=={0}을 입력해주세요.
Range={0}, {2} ~ {1} 까지 가능합니다.
Max=최대 {1}까지 가능합니다.
#==ObjectError==
totalPriceMin.item=상품의 가격 * 수량의 합은 {0}원 이상이어야 합니다. 현재 값 = {1}
totalPriceMin=전체 가격은 {0}원 이상이어야 합니다. 현재 값 = {1}
#==타입 변환 실패시==
typeMismatch.java.lang.Integer=숫자를 입력해주세요!
typeMismatch=타입 오류입니다.
{0} = 필드명
spring.messages.basename=messages, errors
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
//Object Error
if(item.getPrice() != null && item.getQuantity() != null){
int resultPrice = item.getPrice() * item.getQuantity();
if(resultPrice<10000){
bindingResult.reject("totalPriceMin", new Object[]{10000,resultPrice}, null);
}
}
//검증에 실패하면 다시 입력 폼으로
if(bindingResult.hasErrors()){
log.info("errors = {}", bindingResult);
return "validation/v3/addForm";
}
//성공 로직
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v3/items/{itemId}";
}
@ModelAttribute는 필드 단위로 세밀하게 바인딩이 적용된다. 특정 필드가 바인딩 되지 않아도 나머지 필드는 정상 바인딩 되고, Validator를 사용한 검증도 적용할 수 있다.
@RequestBody는 전체 객체 단위로 적용되기 때문에 HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 예외가 발생한다. 컨트롤러도 호출되지 않고, Validator도 적용할 수 없다.