김영한 강사님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술을 듣고 정리한 내용입니다. 자세한 내용은 강의를 참고해주세요
Bean Validation이 있다implementation 'org.springframework.boot:spring-boot-starter-validation'
spring-boot-starter-validation 의존관계를 추가하면 라이브러리가 추가 된다.spring-boot-starter-validation로 인해 자동으로 @Bean Validator을 인지하고 스프링에 통합한다LocalValidaotrFactoryBean을 글로벌 Validator로 등록해 놨는데@NotNull같은 에노테이션을 보고 검증을 수행한다@Valid, @Validated가 적용된 객체를 검증하는데FieldError, ObjectError을 생성해서 BindingResult에 담아준다과정을 자세히 봐보면...
@ModelAttribute 에노테이션으로 인해, request parameters를 객체의 필드에 넣어준다typeMismatch(스프링 꺼) -> field Error에 추가@Validator 적용@Validator을 적용해, 검증사항을 확인하는데FieldError, ObjectError을 생성해서 BindingResult에 담아준다이제 코드를 확인해보자
@Data
public class Item {
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
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;
}
}
@Validate에서 확인돼, 검증된다하이버네이트 Validator 관련 링크
공식 사이트: http://hibernate.org/validator/
공식 메뉴얼: https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/
검증 애노테이션 모음: https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#validator-defineconstraints-spec

MessageCodesResolver에서 생성한 field Error랑 비슷한데???NotBlank.item.itemName = 상품 이름은 빈칸 없이 적어주세요!!!
NotBlank={0} 공백X
- 이렇게 error.properties에 추가해준다!
@ScrpitAssert()를 적용해, Bean Validator을 사용할 수 있다 @Data
@ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >=
10000")
Object Error에서는 사용하기 어렵다jdk8 ~ jdk14의 JVM 상에서 사용되는Nashorn 엔진은 javascript를 지원하는데, jdk14 이후 버전부터는 javascript가 지원되지 않는 GraalVM 을 사용한다고 합니다.스프링 부트 3이후에는 java 17 이상을 사용하는 것이 필수조건으로 되어있기 때문에, 더는 스프링 부트 3에서는 @ScriptAssert를 이용한 자바스크립트 표현식을 사용할 수 없다
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
//특정 필드 예외가 아닌 전체 예외
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000,resultPrice}, null);
}
}

BeanValidation의 groups 기능

BeanValidation 에노테이션 안에, groups라는 파라미터를 이용해 아까 만든 인터페이스의 클래스를 지정해준다BeanValidation은 {}안에 모두 넣어주면 된다
@Validated안에 value파라미터 안에 인터페이스의 클래스를 지정해준다@Valid에서는 groups기능을 지원하지 않는다ItemSaveForm, ItemUpdateForm은 html form데이터에서 받을 때만 사용할 객체이기 때문에 컨트롤러 레벨까지만 사용할 것이다!!!
@Data
public class Item {
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
}
@Data
public class ItemSaveForm {
@NotBlank
private String itemName;
@NotNull
@Range(min=1000, max=1000000)
private Integer price;
@NotNull
@Max(value = 9999)
private Integer quantity;
}
@Data
public class ItemUpdateForm {
@NotNull
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min=1000, max=1000000)
private Integer price;
// 수정시에는 수량은 자유롭게 변경할 수 있다.
private Integer quantity;
}
Bean Validation을 적용시켜준다 @PostMapping("/add")
public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, BindingResult
bindingResult, RedirectAttributes redirectAttributes) {
// 특정필드가 아닌 복합 룰 검증
if (form.getPrice() != null && form.getQuantity() != null) {
int resultPrice = form.getPrice() * form.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{100000, resultPrice}, null);
}
}
if (bindingResult.hasErrors()) {
log.info("errors={}", bindingResult);
return "validation/v4/addForm";
}
//성공 로직
Item item = new Item();
item.setItemName(form.getItemName());
item.setPrice(form.getPrice());
item.setQuantity(form.getQuantity());
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v4/items/{itemId}";
}
@PostMapping("/{itemId}/edit")
public String editV2(@PathVariable Long itemId, @Validated @ModelAttribute("item") ItemUpdateForm form, BindingResult bindingResult) {
// 특정필드가 아닌 복합 룰 검증
if (form.getPrice() != null && form.getQuantity() != null) {
int resultPrice = form.getPrice() * form.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{100000, resultPrice}, null);
}
}
if (bindingResult.hasErrors()){
log.info("errors={}",bindingResult);
return "validation/v4/editForm";
}
Item itemParam = new Item(form.getItemName(), form.getPrice(), form.getPrice()); // 생성자를 오버라이딩 해놓은 이유
itemRepository.update(itemId, itemParam);
return "redirect:/validation/v4/items/{itemId}";
}
}
@ModelAttribute("item") ItemSaveForm form이렇게 수정해서 객체를 방금 만든 요청 객체로 바꿔준다@ModdleAttribute에 item을 명시해줘서 view코드를 수정하지 않는다@ModelAttribute를 이용해 HTTP 요청 파라미터를 처리했다@RequestBody를 이용해 API요청에서도 Bean 검정을 사용해보자!!
HttpMessageConverter가 json형식을 itemSavedForm형태로 form객체로 만들어 주었다itemSavedForm자체를 만들지 못하였고 HttpMessageConverter가 json형식을 itemSavedForm형태로 form객체로 만들어 준게 성공하였다Validator들에서 검증 오류가 나는 경우이다
API 방식에서도 Bean Validator을 사용할 수 있다를 알자!@ModelAttribute는 각각의 필드 단위로 세밀하게 적용된다.HttpMessageConverter 는 @ModelAttribute 와 다르게 각각의 필드 단위로 적용되는 것이 아니라, 전체 객체단위로 적용된다.