코딩성장스토리

스프링 Bean Validation 본문

백 엔드/spring

스프링 Bean Validation

까르르꿍꿍 2022. 5. 9. 01:32

김영한선생님의 MVC2편

Bean Validaiton

전 글에서 봤듯이 검증기능 코드들은 상당히 길고 복잡하다.

그리고 또한 중복되어서 쓰이는 기능들이 많이 보인다. 이를 애노테이션으로 쉽게 지원을 해주는 것이 Bean Validaiton이다. 아래에서 예시를 보자

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;
 /

위의 코드는 이렇게 해석이 가능하다

@NotBlank : 빈값 + 공백만 있는 경우를 허용하지 않는다.

@NotNull : null 을 허용하지 않는다.

@Range(min = 1000, max = 1000000) : 범위 안의 값이어야 한다.

@Max(9999) : 최대 9999까지만 허용한다.

 

검증 순서

1. @ModelAttribute 각각의 필드에 타입 변환 시도

(1. 성공하면 다음으로 2. 실패하면 typeMismatch 로 FieldError 추가)

2. Validator 적용

 

@ModelAttribute 각각의 필드 타입 변환시도 변환에 성공한 필드만 BeanValidation 적용

 

Bean Validation을 적용하고 bindingResult 에 등록된 검증 오류 코드

 

@NotBlank

NotBlank.item.itemName

NotBlank.itemName

NotBlank.java.lang.String

NotBlank

@Range

Range.item.price

Range.price

Range.java.lang.Integer

Range

 

메시지 코드

#Bean Validation 추가
NotBlank={0} 공백X
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}

{0} 은 필드명이고, {1} , {2} ...은 각 애노테이션 마다 다르다.

 

BeanValidation 메시지 찾는 순서

1. 생성된 메시지 코드 순서대로 messageSource 에서 메시지 찾기

2. 애노테이션의 message 속성 사용 @NotBlank(message = "공백! {0}")

3. 라이브러리가 제공하는 기본 값 사용 공백일 수 없습니다

 

동일한 모델 객체를 각각 다르게 검증해야 할떄

이럴때 구분지으면서 구분할수 있는 방법이 2가지 있다..

1.BeanValidation의 groups 기능을 사용한다.

2.Item을 직접 사용하지 않고, ItemSaveForm, ItemUpdateForm 같은 폼 전송을 위한 별도의 모델 객체를 만들어서 사용한다

 

1번째 방법-groups기능 (savecheck,updatecheck 빈 인터페이스 생성후)

@Data
public class Item {
 @NotNull(groups = UpdateCheck.class) //수정시에만 적용
 private Long id;
 @NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
 private String itemName;
 @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
 @Range(min = 1000, max = 1000000, groups = {SaveCheck.class,
UpdateCheck.class})
 private Integer price;
 @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
 @Max(value = 9999, groups = SaveCheck.class) //등록시에만 적용
 private Integer quantity;
 public Item() {
 }
 public Item(String itemName, Integer price, Integer quantity) {
 this.itemName = itemName;
 this.price = price;
 this.quantity = quantity;
 }
}
@PostMapping("/add")
public String addItemV2(@Validated(SaveCheck.class) @ModelAttribute Item item,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
 //...
}

위의 코드들은 주어진 인터페이스에 따라 객체다르게 검증하는 방법이다. 하지만 위와 같은 방법은 복잡하다

 

 

2번쨰 방법 Form 전송 객체 분리

폼 데이터 전달에 Item 도메인 객체 사용

HTML Form -> Item -> Controller -> Item -> Repository

장점: Item 도메인 객체를 컨트롤러, 리포지토리 까지 직접 전달해서 중간에 Item을 만드는 과정이 없어서 간단하다.

단점: 간단한 경우에만 적용할 수 있다. 수정시 검증이 중복될 수 있고, groups를 사용해야 한다.

폼 데이터 전달을 위한 별도의 객체 사용

HTML Form -> ItemSaveForm -> Controller -> Item 생성 -> Repository

장점: 전송하는 폼 데이터가 복잡해도 거기에 맞춘 별도의 폼 객체를 사용해서 데이터를 전달 받을 수 있다. 보통 등록과, 수정용으로 별도의 폼 객체를 만들기 때문에 검증이 중복되지 않는다.

단점: 폼 데이터를 기반으로 컨트롤러에서 Item 객체를 생성하는 변환 과정이 추가된다.

 

아래 처럼 별도의 전송객체 만들기

 

@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;
}

그리고 아래는 컨트롤러 코드 (전달 객체에서 받은 데이터 본 객체로 전달하기 위한 생성 코드 필요)

@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[]{10000,
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}";
 }

'백 엔드 > spring' 카테고리의 다른 글

스프링 - 필터 , 인터셉터  (0) 2022.05.14
스프링 -쿠키,세션  (0) 2022.05.13
스프링 vaildation  (0) 2022.05.09
스프링 메시지,국제화  (0) 2022.05.06
스프링 타임리프 스프링 통합기능  (0) 2022.05.06