| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- dockerhub
- MSA
- @Transactional
- 컨테이너
- Spring
- redis
- 스프링 부트
- CORS
- JdbcTemplate
- Web
- DI
- mybatis
- @ComponentScan
- JWT
- securitycontextholderfilter
- Routing Key
- kafka
- 쿠버네티스
- 서블릿 컨테이너
- JPQL
- Spring Data JPA
- JPA
- Dead Letter Queue
- docker
- Spring Container
- 페이징
- AWS
- 지연 로딩
- docker compose
- DLQ
- Today
- Total
look-forest
Bean Validation 본문
검증 기능을 매번 코드로 작성하는 것은 상당히 번거롭다.
특히 특정 필드에 대한 검증 로직은 대부분 빈 값인 지 아닌지, 특정 크기를 넘는지 아닌지와 같이 매우 일반적인 로직이다
즉, 정형화 할 수 있다.
이런 검증 로직을 애노테이션을 활용해 모든 프로젝트에 적용할 수 있게 공통화, 표준화 한 것이 바로 Bean Validation이다.
※ 여기서 말하는 Bean은 Spring Bean이 아니라 Java Bean이다. (캡슐화를 통해 데이터와 메소드를 하나의 객체로 묶는 형태. 주로 getter/setter를 통해 필드 접근)
Bean Validation 적용
spring-boot-starter-validation 의존관계를 추가하면 Bean validation을 사용할 수 있다.
기존에 만든 validator는 지우고, bean validator를 적용하자.

1. 검증 로직 적용

2.스프링 MVC에 Bean validator 적용(자동)
스프링부트가 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합,
자동으로 글로벌 Validator로 등록한다.(LocalValidatorFactoryBean을 글로벌 Validator로 등록)
이 Validator는 @NotNull 같은 애노테이션을 보고 검증을 수행
3. 이렇게 글로벌 Validator가 적용되어 있기 때문에, @Valid , @Validated 만 적용하면 된다.
검증 오류가 발생하면, FieldError , ObjectError 를 생성해서 BindingResult 에 담아준다
※ 일반적으로 @Validated 또는 @Valid는 메서드 파라미터로 DTO를 받는 경우에 사용하여, Spring이 자동으로 유효성 검사를 수행하도록 하는 역할을 한다.
Bean Validation - 에러 코드
Bean Validation을 적용하고 bindingResult 에 등록된 검증 오류 코드를 보자.
오류 코드가 애노테이션 이름으로 등록된다

NotBlank 라는 오류 코드를 기반으로 MessageCodesResolver 를 통해 다양한 메시지 코드가 순서대로 생성된다.
따라서 errors.properties에 오류코드를 등록해주면 된다.

Bean Validation - 오브젝트 오류
Bean Validation에서 특정 필드(FieldError)가 아닌 해당 오브젝트 관련 오류(ObjectError)는
@ScriptAssert() 를 사용하면 된다.

그런데 실제 사용해보면 제약이 많고 복잡하다. 그리고 실무에서는 검증 기능이 해당 객체의 범위를 넘어서는 경우들도 종종 등장하는데, 그런 경우 대응이 어렵다.
따라서 오브젝트 오류(글로벌 오류)의 경우 @ScriptAssert 을 억지로 사용하는 것 보다는
다음과 같이 오브젝트 오류 관련 부분만 직접 자바 코드로 작성하는 것을 권장한다.

Bean Validation 한계 - 수정시 검증 요구사항
데이터를 등록할 때와 수정할 때는 요구사항이 다를 수 있다.
뿐만 아니라 ID 같은 경우는 등록 시에는 없지만, 수정시에는 필수 값이다.(요청은 탈취 가능하므로, 서버에서 검증해야)
동일한 모델 객체를 등록할 때와 수정할 때 각각 다르게 검증하는 방법은 아래 두가지가 있다.
- BeanValidation의 groups 기능을 사용한다.
- Item을 직접 사용하지 않고, ItemSaveForm, ItemUpdateForm 같은 폼 전송을 위한 별도의 모델 객체를 만들어서 사용
1. groups 적용
1) group으로 사용할 인터페이스 생성
package hello.itemservice.domain.item;
public interface SaveCheck {
}
package hello.itemservice.domain.item;
public interface UpdateCheck {
}
2) Item - groups 적용

3) 컨트롤러 로직에 groups 적용

한계 : groups 기능을 사용하니 Item 은 물론이고, 전반적으로 복잡도가 올라갔다.
사실 groups 기능은 실제 잘 사용되지는 않는데, 그 이유는 실무에서는 주로 다음에 등장하는 등록용 폼 객체와 수정용 폼 객체를 분리해서 사용하기 때문이다.
2. Form 전송 객체 분리
실무에서는 groups를 잘 사용하지 않는다. 보통 등록/수정용으로 별도의 폼 객체를 만들기 때문에 검증이 중복되지 않는다
소위 "Hello World" 예제에서는 폼에서 전달하는 데이터와 Item 도메인 객체가 딱 맞는다. 하지만 실무에서는 회원 등록시 회원과 관련된 데이터만 전달받는 것이 아니라, 약관 정보도 추가로 받는 등 Item 과 관계없는 수 많은 부가 데이터가 넘어온다. 그래서 보통 Item을 직접 전달받는 것이 아니라, 복잡한 폼의 데이터를 컨트롤러까지 전달할 별도의 객체를 만들어서 전달한다.
1) 등록/수정 용 폼 생성


2) 컨트롤러 변경

Bean Validation - HTTP 메시지 컨버터
@Valid, @Validated 는 HttpMessageConverter (@RequestBody)에도 적용할 수 있다.
참고
@ModelAttribute 는 HTTP 요청 파라미터(URL 쿼리 스트링, POST Form)를 다룰 때 사용한다.
@RequestBody 는 HTTP Body의 데이터를 객체로 변환할 때 사용한다. 주로 API JSON 요청을 다룰 때 사용한다
API용 컨트롤러 생성

API의 경우 3가지 경우를 나누어 생각해야 한다.
- 성공 요청: 성공
- 실패 요청: JSON을 객체로 생성하는 것 자체가 실패함
- 검증 오류 요청: JSON을 객체로 생성하는 것은 성공했고, 검증에서 실패함
2. 실패 요청 응답 값 : HttpMessageConverter에서 요청 JSON을 ItemSaveForm 객체로 생성하는데 실패하는 경우

@ModelAttribute vs @RequestBody
@ModelAttribute 는 필드 단위로 정교하게 바인딩이 적용된다. 특정 필드가 바인딩 되지 않아도 나머지 필드는 정상 바인딩 되고, Validator를 사용한 검증도 적용할 수 있다.
@RequestBody 는 HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계 자체가 진행되지 않고 예외가 발생한다. 컨트롤러도 호출되지 않고, Validator도 적용할 수 없다.
참고 자료 & 이미지 출처
스프링 MVC 2편 (김영한 님)
'Spring > Spring MVC - 웹 개발 활용 기술' 카테고리의 다른 글
| 예외 처리와 오류 페이지 (0) | 2024.08.10 |
|---|---|
| 로그인 처리2 - 필터, 인터셉터 (0) | 2024.08.10 |
| 로그인 처리 - 쿠키, 세션 (0) | 2024.08.06 |
| 검증 (validation) (0) | 2024.07.16 |
| 타임 리프 (1) | 2023.12.17 |