일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 쉘 스크립트
- m:n
- 컴파일 타임 상수
- JPA
- allocationSize
- DTO
- compgen
- 쿠키
- 프로그래머스
- spring
- application layer
- intelij spring config
- BindingResult
- 무한정 대기
- 파이썬
- 커밋 되돌리기
- 티스토리챌린지
- 오블완
- 알고리즘
- 편향된 지수
- 런타임 상수
- JDBC
- Git
- @SubscribeMapping
- 은행원알고리즘
- 기본키 전략
- API
- 백준
- @Autowired
- 리눅스
- Today
- Total
둘셋 개발!
[spring mvc 2편-웹 개발 활용 기술 - 4 (2) ] 검증2 BeanValidation 본문
Bean Validation이란?
: 검증 애노테이션과 여러 인터페이스의 모음이다.
Item 클래스에 Bean Validation을 적용해보자
@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;
}
}
검증 기능을 매번 코드로 작성하지 않고 Bean Validation을 잘 활용하면 애노테이션 하나로 검증로직을 편리하게 적용할 수 있다.
@NotBlank : 빈값 + 공백만 있는 경우를 허용하지 않는다.
@NotNull : null을 허용하지 않는다.
@Range(min = 1000, max=1000000) : 범위 안의 값이어야 한다
@Max(9999) : 최대 9999까지만 허용한다.
스프링 MVC는 어떻게 Bean Validator를 사용할까?
: spring-boot-starter-validation라이브러리를 넣으면 스프링부트가 자동으로 Bean Validator를 인지하고 스프링에 통합한다.
: 스프링부트가 Bean Validator를 자동으로 글로벌 Validator로 등록해준다. 그래서 @Valid, @Validated만 적용하면 된다.
: 검증오류가 발생하면 FieldError, ObjectError를 생성해서 BindingResult에 담아준다.
( + @Valid와 @Validated의 차이 )
@Valid는 자바 표준 검증 애노테이션이고 @Validated는 스프링 전용 검증 애노테이션이다.
그리고 @Validated는 내부에 groups라는 기능을 포함한다(뒤에 설명)
Bean Validation을 사용했을 시 검증 순서
1. @ModelAttribute 각각의 필드에 타입 변환 시도
-성공하면 다음으로
-실패하면 typeMismatch로 FieldError추가
2. Bean Validation 적용
Bean Validation 에러코드
오류코드는 애노테이션 이름으로 등록된다.
만약에 @NotBlank의 검증을 통과하지 못하면 NotBlank라는 오류코드 기반으로 MessageCodesResolver를 메세지 코드가생성된다.
예시) @NotBlank오류
-NotBlank.item.itemName
-NotBlank.itemName
-NotBlank.java.lang.String
-NotBlank
Bean Validation이 메세지 찾는 순서
1. 생성된 메세지 코드 순서대로 MessageSource에서 메세지 찾기
2. 애노테이션 message속성 사용 -> @NotBlank(message = "공백입니다! {0})
3. 라이브러리가 제공하는 기본 값 사용
Bean Validation 오브젝트 오류
: 특정필드의 오류가 아닌 특정필드를 넘어선 오류인 해당 오브젝트 관련 오류는
@ScriptAssert()를 사용하면 된다.
@Data
@ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >= 10000")
public class Item{
//...
}
가격과 수량의 곱했을 때 10000을 넘어야 한다는 뜻이다.
이를 실행해보면 메세지 코드는
ScriptAssert.item
ScriptAssert
가 생성된다.
하지만!! 실제로 사용해보면 제약이 많고 복잡하다. 그리고 실무에서는 검증 기능이 해당 객체의 범위를 넘어서는 경우들이 등장하는데, 그런 경우 대응이 어렵다.
따라서 @ScriptAssert를 억지로 사용하는 것 보다 오브젝트 오류 관련 부분만 직접 자바코드로 작성하는 것을 권장한다고 한다.
BeanValidation의 한계
만약 Item을 생성할 때 처음 생성할 때와 나중에 수정할 때의 검증 요청 사항이 다르면 어떻게 해야할까?
이럴때 방법이 2가지가 있다.
1. BeanValidation의 groups기능을 사용
2. Item을 직접 사용하지 않고, ItemSaveForm, ItemUpdateForm같은 폼 전송을 위한 별도의 모델 객체를 만든다(권장!)
먼저 groups기능을 사용해보겠다.
1. BeanValidation groups기능 사용
등록과 수정 요구사항이 다른 예시:
(등록시 요구사항)
-타입 검증
가격, 수량에 문자가 들어가면 검증 오류 처리
-필드 검증
상품명: 필수,
가격: 1000원 이상, 1백만원 이하,
수량: 최대 9999
-특정 필드의 범위를 넘어서는 검증
가격 * 수량의 합은 10,000원 이상
(수정시 요구사항)
등록시에는 quantity 수량을 최대 9999까지 등록할 수 있지만 수정시에는 수량을 무제한으로 변경할 수 있다.
등록시에는 id 에 값이 없어도 되지만, 수정시에는 id 값이 필수이다.
-등록시 groups 생성
public interface SaveCheck {}
-수정시 groups생성
public interface UpdateCheck {}
-item groups 적용
@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;
}
}
-Controller에 groups적용
@PostMapping("/add")
public String addItem(@Validated(SaveCheck.class) @ModelAttribute Item item,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
//...
}
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @Validated(UpdateCheck.class)
@ModelAttribute Item item, BindingResult bindingResult) {
//...
}
2. Form전송 객체 분리
폼전송 객체를 분리해야 하는 이유:
실무에서는 1방법 보다 form전송 객체 분리 방법을 더 많이 쓴다.
왜냐하면 등록시 폼에서 전달하는 데이터가 Item도메인 객체와 딱 맞지 않다.
예를 들어 회원등록시 회원과 관련된 데이터만 전달받는 것이 아니라, 약관 정보도 추가로 받는 등 Item과 관계없는 수많은 다른 데이터가 넘어온다.
그래서 복잡한 폼의 데이터를 컨트롤러까지 전달한 별도의 객체를 만들어서 전달한다.
ex) HTML form -> ItemSaveForm -> Controller -> Item 생성 -> Repository
item을 등록할 때의 폼 객체와 수정할 때의 폼 객체를 따로 만들 것이다. 우선 item클래스에 있는 검증코드를 제거한다.
-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;
}
-item 수정용 폼
@Data
public class ItemUpdateForm {
//수정에서는 id값이 널이면 안된다.
@NotNull
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
//수정에서는 수량은 자유롭게 변경할 수 있다.
private Integer quantity;
}
이제 컨트롤러에서는 등록 시,
1. 등록 폼 객체를 바인딩 해줘야 한다.
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form,
BindingResult bindingResult, RedirectAttributes redirectAttributes) {
//...
}
@ModelAttribute로 form 객체를 받아온다.
이때 주의할 점은 @ModelAttribute는 바인딩 하는 객체의 타입에서 앞글자만 소문자로 바꾼 itemSaveForm을 바인딩 해오기 때문에
("item")이라고 꼭 써줘야 한다.
2. 폼 객체를 item으로 변환
//성공 로직
Item item = new Item();
item.setItemName(form.getItemName());
item.setPrice(form.getPrice());
item.setQuantity(form.getQuantity());
Item savedItem = itemRepository.save(item);
수정도 마찬가지 이다.
HTTP 메세지 컨버터
@Valid, @Validated는 HttpMessageConverter(@RequestBody) 에도 적용할 수 있다.
(@RequestBody는 HTTP Body의 데이터를 객체로 변환할 때 사용)
-컨트롤러에서 @RequestBody 사용
@PostMapping("/add")
public Object addItem(@RequestBody @Validated ItemSaveForm form,
BindingResult bindingResult) {
//...
}
api의 경우 3가지 경우를 나눠서 생각해야한다.
- 성공요청 : 성공
- 실패 요청 : json을 객체로 생성하는 것 자체가 실패
- 검증 오류 요청 : json을 객체로 생성하는 것은 성공, 검증에서 실패
실패 요청에서 json을 객체로 생성하는 것을 실패하는 이유는 특정 필드에 타입이 맞지 않는 오류가 발생했기 때문이다. 따라서 객체를 만들 수 없는 것이다. 이를 해결하는 방법은 이후 포스팅에서 설명한다.
여기서 @ModelAttribute와 @RequestBody의 차이가 발생한다.
- @ModelAttribute : 각각 필드 단위로 세밀하게 적용되기 때문에 특정필드에도 타입오류가 나더라도 나머지 필드는 정상처리 가능
- HttpMessageConverter : 각각 필드 단위로 적용되는 것이 아니라, 전체 객체 단위로 적용
(참고 : 인프런 김영한 강사님 - 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 )
'SPRING > MVC' 카테고리의 다른 글
[spring mvc 2편-웹 개발 활용 기술 - 6 ] 예외처리와 오류 페이지 (0) | 2022.02.16 |
---|---|
[spring mvc 2편-웹 개발 활용 기술 - 5 (2) ] 로그인 처리 - 필터,인터셉터 (0) | 2022.02.08 |
[spring mvc 2편-웹 개발 활용 기술 - 5 (1) ] 로그인 처리 - 쿠키, 세션 (0) | 2022.02.05 |
[spring mvc 2편-웹 개발 활용 기술-4(1) ] 검증 Validation (2) | 2022.01.26 |
[spring mvc 2편-웹 개발 활용 기술-3] 메세지, 국제화 (0) | 2022.01.16 |