[TIL] #5. 검증2 - Bean Validation ②

Bean Validation - 수정에 적용

상품 수정에도 빈 검증(Bean Validation)을 적용해보자!

🔗 전체 코드 확인하기

  • edit(): Item 모델 객체에 @Validated를 추가하자.
  • 검증 오류가 발생하면 editForm으로 이동하는 코드 추가

Bean Validation - 한계

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

✔ 등록 시 기존 요구사항

  • 타입 검증
    : 가격, 수량에 문자가 들어가면 검증 오류 처리
  • 필드 검증
    • 상품명: 필수, 공백 X
    • 가격: 1000원 ~ 1백만원
    • 수량: 최대 9999
  • 특정 필드의 범위를 넘어서는 검증
    : 가격 * 수량의 합은 10,000원 이상

✔ 수정 시 요구사항


수정 요구사항 적용

package hello.itemservice.domain.item;

@Data
public class Item {

    @NotNull //수정 요구사항 추가
    private Long id;
    
    @NotBlank
    private String itemName;
    
    @NotNull
    @Range(min = 1000, max = 1000000)
    private Integer price;
    
    @NotNull
    //@Max(9999) //수정 요구사항 추가
    private Integer quantity;
    
    ...
}

수정 요구사항을 적용하기 위해 다음을 적용했다.

  • id: @NotNull 추가
  • quantity: @Max(9999) 제거

Bean Validation - groups

동일한 모델 객체를 등록할 때와 수정할 때 각각 다르게 검증하는 방법에 대해 알아보자!

  1. BeanValidationgroups 기능을 사용한다.
  2. Item을 직접 사용하지 않고, ItemSaveForm, ItemUpdateForm 같은
    폼 전송을 위한 별도의 모델 객체를 만들어서 사용한다.

BeanValidation - groups 기능 사용

이런 문제를 해결하기 위해 Bean Validationgroups라는 기능을 제공한다.
groups 기능을 이용해서 등록 시에 검증할 기능과 수정 시에 검증할 기능을 각각 그룹으로 나누어 적용할 수 있다.

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;
    
    ...
}

실행 결과

🔗 전체 코드 확인하기

📌 groups 기능 정리

  • groups 기능을 사용해서 등록과 수정 시에 각각 다르게 검증할 수 있었다.
  • 하지만 사실 groups 기능은 잘 사용하지 않는다.
    • 사용하기 복잡하고,
    • 실무에서는 등록용 폼 객체와 수정용 폼 객체를 분리해서 사용하기 때문에 groups를 적용할 일이 없기 때문이다!

Form 전송 객체 분리 - 프로젝트 준비 V4

🔗 전체 코드 확인하기

실행 결과


Form 전송 객체 분리 - 소개

실무에서는 groups를 잘 사용하지 않는다.
등록 시 폼에서 전달하는 데이터가 Item 도메인 객체와 딱 맞지 않기 때문이다.

그래서 보통 Item을 직접 전달받는 것이 아니라,
복잡한 폼의 데이터를 컨트롤러까지 전달할 별도의 객체를 만들어서 전달한다.

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

HTML Form → Item → Controller → Item → Repository

  • 장점: Item 도메인 객체를 컨트롤러, 리포지토리까지 직접 전달해서 중간에 Item을 만드는 과정이 없어서 간단하다.
  • 단점: 간단한 경우에만 적용할 수 있다. 수정 시 검증이 중복될 수 있고, groups를 사용해야 한다.

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

HTML Form → ItemSaveForm → Controller → Item 생성 → Repository

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

Item 도메인 객체를 폼 전달 데이터로 사용하고, 그대로 쭉 넘기면 편리하겠지만,
실무에서는 Item의 데이터만 넘어오는 것이 아니라 무수한 추가 데이터가 넘어온다.
그리고 더 나아가서 Item을 생성하는데 필요한 추가 데이터를 데이터베이스나 다른 곳에서 찾아와야 할 수도 있다.

따라서 이렇게 폼 데이터 전달을 위한 별도의 객체를 사용하고, 등록, 수정용 폼 객체를 나누면 등록, 수정이
완전히 분리되기 때문에 groups를 적용할 일은 드물다.


Form 전송 객체 분리 - 개발

Item 대신 ItemSaveForm을 전달받는다.
@Validated로 검증을 수행하고, BindingResult로 검증 결과를 받는다.

🔗 전체 코드 확인하기


Bean Validation - HTTP 메시지 컨버터

@Valid, @ValidatedHttpMessageConverter(@RequestBody)에도 적용할 수 있다.

🔗 전체 코드 확인하기

Postman을 이용해서 테스트

📌 API

  • 성공 요청: 성공
  • 실패 요청: JSON을 객체로 생성하는 것 자체가 실패함.
  • 검증 오류 요청: JSON을 객체로 생성하는 것은 성공했고, 검증에서 실패함.

1. 성공 요청

2. 실패 요청

price 값에 숫자가 아닌 문자를 전달해서 실패하도록 만들자.

HttpMessageConverter에서 요청 JSON을 Item 객체를 생성하지 못한다.

이 경우는, 객체가 생성되지 않았기 때문에 컨트롤러 자체가 호출되지 않고 그 전에 예외가 발생한다. 물론 Validator도 실행되지 않는다.

3. 검증 오류 요청

이번에는 수량(quantity) 검증 오류가 발생하도록 해보자.

return bindingResult.getAllErrors();ObjectErrorFieldError를 반환한다.

스프링이 이 객체를 JSON으로 변환해서 클라이언트에 전달했다.


@ModelAttirbute vs. @RequestBody

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

@RequestBodyHttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계 자체가 진행되지 않고 예외가 발생한다.
컨트롤러도 호출되지 않고, Validator도 적용할 수 없다.

좋은 웹페이지 즐겨찾기