Notice
Recent Posts
Recent Comments
Link
관리 메뉴

look-forest

Bean Validation 본문

Spring/Spring MVC - 웹 개발 활용 기술

Bean Validation

studyHub 2024. 8. 3. 17:22

검증 기능을 매번 코드로 작성하는 것은 상당히 번거롭다.

특히 특정 필드에 대한 검증 로직은 대부분 빈 값인 지 아닌지, 특정 크기를 넘는지 아닌지와 같이 매우 일반적인 로직이다

즉, 정형화 할 수 있다.

이런 검증 로직을 애노테이션을 활용해 모든 프로젝트에 적용할 수 있게 공통화, 표준화 한 것이 바로 Bean Validation이다.

※ 여기서 말하는 Bean은 Spring Bean이 아니라 Java Bean이다. (캡슐화를 통해 데이터와 메소드를 하나의 객체로 묶는 형태. 주로 getter/setter를 통해 필드 접근)


Bean Validation 적용

spring-boot-starter-validation 의존관계를 추가하면 Bean validation을 사용할 수 있다.

 

기존에 만든 validator는 지우고, bean validator를 적용하자.

수동으로 직 만든 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 같은 경우는 등록 시에는 없지만, 수정시에는 필수 값이다.(요청은 탈취 가능하므로, 서버에서 검증해야)

 

동일한 모델 객체를 등록할 때와 수정할 때 각각 다르게 검증하는 방법은 아래 두가지가 있다.

  1. BeanValidation의 groups 기능을 사용한다.
  2. 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 적용

@Valid 에는 groups를 적용할 수 있는 기능이 없다. 따라서 groups를 사용하려면 @Validated 를 사용해야 한다.

 

한계 : groups 기능을 사용하니 Item 은 물론이고, 전반적으로 복잡도가 올라갔다.
사실 groups 기능은 실제 잘 사용되지는 않는데, 그 이유는 실무에서는 주로 다음에 등장하는 등록용 폼 객체와 수정용 폼 객체를 분리해서 사용하기 때문이다.

 

2. Form 전송 객체 분리

실무에서는 groups를 잘 사용하지 않는다. 보통 등록/수정용으로 별도의 폼 객체를 만들기 때문에 검증이 중복되지 않는다

소위 "Hello World" 예제에서는 폼에서 전달하는 데이터와 Item 도메인 객체가 딱 맞는다. 하지만 실무에서는 회원 등록시 회원과 관련된 데이터만 전달받는 것이 아니라, 약관 정보도 추가로 받는 등 Item 과 관계없는 수 많은 부가 데이터가 넘어온다. 그래서 보통 Item을 직접 전달받는 것이 아니라, 복잡한 폼의 데이터를 컨트롤러까지 전달할 별도의 객체를 만들어서 전달한다.

 

1) 등록/수정 용 폼 생성

2) 컨트롤러 변경

form 데이터를 Item으로 변환하는 과정은 추가가 필요하다.

 


Bean Validation - HTTP 메시지 컨버터

@Valid, @Validated 는 HttpMessageConverter (@RequestBody)에도 적용할 수 있다.

참고
@ModelAttribute 는 HTTP 요청 파라미터(URL 쿼리 스트링, POST Form)를 다룰 때 사용한다.
@RequestBody 는 HTTP Body의 데이터를 객체로 변환할 때 사용한다. 주로 API JSON 요청을 다룰 때 사용한다

 

API용 컨트롤러 생성

 

API의 경우 3가지 경우를 나누어 생각해야 한다.

  1. 성공 요청: 성공
  2. 실패 요청: JSON을 객체로 생성하는 것 자체가 실패함
  3. 검증 오류 요청: 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