💡Hibernate Validator 관련 링크
✔️Bean Validation을 사용하기 위해 아래 의존관계를 추가해야 한다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
@NotNull
@Max(9999)
private Integer quantity;
✔️ 스프링 부트에 spring-boot-starter-validation
라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다.
✔️ 스프링 부트는 LocalValidatorFactoryBean
을 글로벌 Validator로 등록한다. 이 Validator가 @NotNull 같은 어노테이션을 보고 검증을 수행한다.
✔️ 이렇게 글로벌 Validator가 등록되어 있기 때문에 @Valid, @Validated 어노테이션만 적용하면 된다.
✔️ 검증 오류가 발생하면 FieldError, ObjectError를 생성해서 BindingResult에 담아준다.
❗ 주의
💡 Bean Validator는 바인딩에 실패한 필드는 Bean Validation을 적용하지 않는다. (검증 자체가 값이 정상적으로 들어와야 검증이 의미가 있기 때문)
✔️ Bean Validation이 제공하는 기본 오류 메시지를 원하는대로 변경할 수도 있다.
✔️ Bean Validation을 적용하고 오류가 발생하면 BindingResult에 아래와 같이 오류 코드를 기반으로 메시지 코드가 생성된다.
✔️ .properties 파일에 원하는 메시지로 등록할 수 있다.
#Bean Validation 추가
NotBlank={0} 공백X
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}
✔️ Bean Validation에서 필드 오류(Field)가 아닌 오브젝트 관련 오류(ObjectError)를 어떻게 처리하는지 알아본다.
@Data
@ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >= 10000")
public class Item {
//...
}
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMain", new Object[]{10000, resultPrice}, null);
}
}
✔️ 동일한 모델 객체를 등록할 때와 수정할 때 각각 다르게 검증하는 방법을 알아본다.
✔️ 검증 방법 2가지
//등록할 때 사용할 그룹
public interface SaveCheck {
}
//수정할 때 사용할 그룹
public interface UpdateCheck {
}
//Item 엔티티
@NotNull(groups = UpdateCheck.class) //수정시에만 적용
private Long id;
@NotNull(groups = {SaveCheck.class, UpdateCheck.class})
@Max(value = 9999, groups = SaveCheck.class) //등록시에만 적용
private Integer quantity;
// add 컨트롤러 메소드
@PostMapping("/add")
public String addItemV2(@Validated(SaveCheck.class) @ModelAttribute Item item,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
//...
}
✔️ groups 기능은 코드가 이것저것 추가되고 복잡도가 올라간다. 그래서 실무에서는 주로 다음에 정리할 Form 전송 객체를 분리해서 사용한다.
✔️ 아래와 같이 폼 전송 객체를 별도로 생성한다.
//ItemSaveForm 클래스
@Data
public class ItemSaveForm {
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
@NotNull
@Max(value = 9999)
private Integer quantity;
}
//컨트롤러 addItem 메소드
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
...
//성공 로직
//전달받은 ItemSaveForm 객체 데이터를 가지고 Item 객체를 새로 생성해준다.
Item item = new Item(form.getItemName(), form.getPrice(), form.getQuantity());
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v4/items/{itemId}";
}
❗주의
✔️ @Valid , @Validated 는 HttpMessageConverter ( @RequestBody )에도 적용할 수 있다.
@Slf4j
@RestController
@RequestMapping("/validation/api/items")
public class ValidationItemApiController {
@PostMapping("/add")
public Object addItem(@RequestBody @Validated ItemSaveForm form, BindingResult bindingResult) {
log.info("API 컨트롤러 호출");
if (bindingResult.hasErrors()) {
log.info("검증 오류 발생 errors={}", bindingResult);
return bindingResult.getAllErrors();
}
log.info("성공 로직 실행");
return form;
}
}