일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 다이나믹 프로그래밍
- 도커
- github action
- 다이나믹프로그래밍
- 자바
- CI/CD
- 역방향 반복자
- 트리
- 그리드 알고리즘
- 순열
- 이분탐색
- BFS
- 자료구조
- TCP
- 분할 정복
- dfs
- 분할정복
- 알고리즘
- 그래프
- 그리드
- 스프링
- SQL
- AWS
- GIT
- 브루트포스
- 백준
- 재귀
- Spring
- 컴퓨터 네트워크
- HTTP
- Today
- Total
코딩성장스토리
스프링 Bean Validation 본문
김영한선생님의 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 |