[스프링] 검증2

gyeol·2023년 11월 26일

스프링

목록 보기
31/50
post-thumbnail

김영한 님의 '스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술'을 듣고 적은 글입니다.

Bean Validation

Bean Validation은 특정 구현체가 아닌 기술 표준이다. 쉽게 이야기해서 애노테이션과 여러 인터페이스의 모음이다.
Bean Validation을 구현한 기술 중 일반적으로 사용하는 구현체는 하이버네이트 Validator이다.

Bean Validation을 사용하려면 build.gradle에 의존관계를 추가해야 한다.

implementation 'org.springframework.boot:spring-boot-starter-validation'

이걸 추가하면 라이브러리가 추가 된다.

Jakarta Bean Validation

  • jakarta.validation-api : Bean Validation 인터페이스
  • hibernate-validator : 구현체

javax.validation 으로 시작하면 특정 구현에 관계없이 제공되는 표준 인터페이스고, org.hibernate.validator로 시작하면 하이버네이트 validator 구현체를 사용할 때만 제공되는 검증 기능이다.

Bean Validation 애노테이션

  • @NotBlank : 빈값 + 공백만 있는 경우를 허용하지 않음
  • @NotNull : null 값 허용 안함
  • @Range(min=1000, max=1000000) : 범위 안의 값이어야 함
  • @Max(9999) : 최대 9999까지 허용

스프링 MVC는 어떻게 Bean Validator 사용?

스프링부트가 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다.

LocalValidatorFactoryBean을 글로벌 Validator로 등록한다. 이 검증기는 @NotNull같은 애노테이션을 보고 검증을 수행한다. 이렇게 글로벌 검증기가 적용되어 있기에 @Valid, @Validated 만 적용하면 된다.
검증 오류가 발생하면, FieldError, ObjectError를 생성해 BindingResult에 담아준다.

검증시 @Validated, @Valid 둘 다 사용가능하다. javax.validation.@Valid를 사용하려면 build.gradle 의존관계 추가가 필요하다. @Validated는 스프링 전용 검증 애노테이션이고, @Valid는 자바 표준 검증 애노테이션이다. 둘 중 아무거나 사용해도 되지만 @Validated는 내부에 groups라는 기능을 포함하고 있다.

주의
글로벌 검증기를 직접 등록하면 스프링 부트는 Bean Validator를 글로벌 Validator를 등록하지 않는다. 따라서 애노테이션 기반 빈 검증기가 동작하지 않는다.

검증 순서

  1. @ModelAttribute 각각 필드에 타입 변환 시도
    성공하면 다음으로
    실패하면 typeMismatchFieldError 추가
  2. Validator 적용

-> 즉, 바인딩에 성공한 필드만 Bean Validation이 적용된다.

예)

  • itemName 에 문자 "A" 입력 타입 변환 성공 itemName 필드에 BeanValidation 적용
  • price 에 문자 "A" 입력 "A"를 숫자 타입 변환 시도 실패 typeMismatch FieldError 추가
  • price 필드는 BeanValidation 적용 X

에러 코드

Bean Validation이 기본으로 제공하는 오류 메시지를 좀 더 자세히 변경하고 싶다면 메시지를 따로 등록해주면 된다.

Bean Validation 메시지 찾는 순서

  1. 생성된 메시지 코드 순서대로 messageSource에서 메시지 찾기
  2. 애노테이션의 message 속성 사용 -> ex) @NotBlank(message = "공백!{0}")
  3. 라이브러리가 제공하는 기본 값 사용

한계

데이터를 등록할 때와 수정할 때는 요구사항이 다를 수 있다.

예를 들어 물건을 등록하는 폼과 수정하는 폼을 생각해보면 비슷한 로직이라고 생각할 수 있겠지만 엄연히 다른 데이터들을 요구한다. 따라서 하나의 검증 로직을 사용하면 당연히 검증 조건의 충돌이 발생할 수 밖에 없다.

방법

  1. Bean Validationgroups기능을 사용한다.
  2. 별도의 수정/등록 데이터를 전송할 수 있는 모델 객체를 각각 만들어서 사용한다.

1. BeanValidation groups

위와 같은 한계를 해결하기 위해 Bean Validationgroups 라는 기능을 제공한다.
예를 들어 등록 시 검증할 기능과 수정시에 검증할 기능을 각각 그룹으로 나누어 적용할 수 있다.

하지만 실제 실무에서는 groups기능을 잘 사용하지 않는다. 그 이유는 바로 다음에 등장하는 등록용 폼 객체와 수정용 폼 객체를 분리해 사용하기 때문이다.

2. 각각의 모델 객체를 만들어 사용

실무에서는 groups를 잘 사용하지 않는데, 그 이유는 실무에서는 관련 데이터만 받는 게 아닌 그 외의 부가적인 데이터가 넘어오기에 별도의 객체를 만들어 @ModelAttribute로 사용한다. 이것을 폼 데이터에 전달받고, 이후 컨트롤러에 필요한 데이터를 사용한다.

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

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

  • 장점 : 중간에 Item을 만드는 과정이 없어 간단
  • 단점 : 간단한 경우에만 적용 가능. groups를 사용해야 함

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

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

  • 장점 : 전송하는 폼 데이터가 복잡해도 거기에 맞춘 별도의 폼 객체를 사용해 데이터 전달받을 수 있음. 검증이 중복되지 않음
  • 단점 : 폼 데이터를 기반으로 컨트롤러에서 Item 객체를 생성하는 변환과정이 추가됨.

HTTP 메시지 컨버터

@Valid, @ValidatedHttpMessageConverter (@RequestBody ) 에도 적용 가능하다.

@RequestBody는 HTTP Body의 데이터를 객체로 변환할 때 사용한다. 주로 API JSON 요청을 다룰 때 사용한다.

API의 경우 3가지 경우를 나누어 생각

  • 성공 요청 : 성공
  • 실패 요청 : JSON을 객체로 생성하는 것 자체가 실패
    메시지 컨버터가 폼 객체를 만들어야 API 컨트롤러 호출하고 JSON으로 변환해주는데, 데이터를 잘못 넣어주면 메시지 컨버터가 폼 객체 자체를 못 만들어서 컨트롤러 자체가 호출되지 않고 JSON 객체도 못만듦
  • 검증 오류 요청 : JSON을 객체로 생성하는 것은 성공했으나 검증에서 실패

@ModelAttribute vs @RequestBody

HTTP 요청 파라미터를 처리하는 @ModelAttribute는 각각의 필드 단위로 세밀하게 적용된다. 그래서 특정 필드에 타입이 맞지 않는 오류가 발생해도 나머지 필드는 정상처리할 수 있다.
HttpMessageConverter@ModelAttribute와 다르게 각각의 필드 단위로 적용되는 것이 아닌 전체 객체 단위러 적용된다.
따라서 메시지 컨버터는 작동이 성공해 해당 객체를 만들어야 @Valid, @Validated가 적용된다.

  • @ModelAttribute는 필드 단위로 정교하게 바인딩이 적용된다. 특정 필드가 바인딩되지 않아도 나머지 필드는 정상 바인딩되고, 검증기를 사용한 검증도 적용할 수 있다.

  • @RequestBody는 HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계가 진행되지 않고 예외가 발생한다. 따라서 컨트롤러도 호출되지 않고 검증기도 적용할 수 없다.

profile
공부 기록 공간 '◡'

0개의 댓글