검증

김남연·2025년 8월 18일

SpringMVC

목록 보기
6/8

오류의 종류

타입 변환 오류(가격, 수량에 문자x)

  • 요청 파라미터를 자바 객체 필드에 바인딩할 때 변환에 실패하는 경우
    price = abc를 Integer price에 바인딩할 때
    오류값은 BindingResult에 FieldError 객체로 담김
    BeanValidation까지 도달하지 못함

검증 오류(필수입력, 공백x, 범위 지정)

  • 타입 변환까지는 성공했지만 제약 조건 어노테이션을 어긴 경우
    @Min(1000)인데 price = 500 들어온 경우
    검사 후 검증 오류 시 BindingResult로 반영

글로벌 오류
특정 필드가 아니라 객체 전체 규칙을 위반했을 때 수동으로 추가

if (price * quantity < 10000) {
	bindingResult.reject("totalPriceMin", new Object[]{10000},"총합이 10000원 이하이상이어야 합니다")
    }    

예외케이스(API의 경우)
@RequestBody에서 JSON 파싱 자체가 실패하면 HttpMessageNotReadableException이 터져서 BindingResult로 들어오지 못함

  • 타입오류
    JSON은 Jackson이 @RequestBody DTO로 변환해야 하는데 이 단계에서 오류가 발생하면 MVC의 DataBinder까지 가지도 못함 (=HttpMessageNotReadableException)
    SSR은 타입 실패하더라도 원본값을 알 수 있으므로 BindingResult에 원본값을 담을 수 있지만
    Jackson은 타입이 맞지않으면 파싱 자체가 되지않으므로 값을 몰라 담을 수 없다

  • 검증 실패
    변환이 성공하고 @Valid에서 검증 실패했을 때, MethodArgumentNotValidException이 발생하는데 스프링이 BindingResult에 파라미터를 주입해주지않고 바로 예외를 던져버림
    api의 경우 ssr과 달리 화면 폼을 보여줄 필요없이 JSON응답을 보내면 되기 때문

    예외 핸들러(@RestControllerAdvice)로 잡아서 JSON 응답을 표준화해야한다

오류 처리 과정

일반적인 경우 (상품 등록 예시)
GET /add (상품 등록 폼) -> POST /add (상품 저장) -> Redirect /items/{id}
-> GET /items/{id}

실패
GET /add (상품 등록 폼) -> POST /add 검증실패 -> Model(검증 오류 포함)
-> (상품 등록 폼)

고객에게 다시 상품 등록 폼을 보여주고 어떤 값이 잘못 되었는지 알려주어야함

FieldError, ObjectError

public FieldError(String objectName, String field, String defaultMessage) {}
public ObjectError(String objectName, String defatultMessage) {}

objectName : @ModelAttribute, 오류가 발생한 객체의 이름 (ex item)
field : 오류가 발생한 필드 (ex itemName)

BindingResult

스프링이 제공하는 검증 오류를 보관하는 객체
BindingResult가 있으면 @ModelAttribute에 바인딩 오류가 발생해도 컨트롤러 호출

BindinResult가 없으면 400오류가 발생하면서 컨트롤러호출x 오류 페이지로 이동

  1. 바인딩 실패시 스프링이 FieldError, ObjectError 생성해서 BindingResult에 넣어줌
  2. 개발자가 직접
  3. Validator

BindingResult는 검증할 대상 바로 다음에 와야한다.
@ModelAttribute Item item, @BindingResult bindingResult...

오류값 유지

필드의 타입 등으로 바인딩 실패시 이를 저장할 수가 없다.
FieldError ObjectError는 이러한 값을 담을 수 있다 (생성자가 따로있음)

Bean Validation

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
}
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttribute redirectAttributes) {
	if (bindingResult.hasErrors()) {
    	log.info("errors = {}", bindingReulst);
    	return "items/addForm";
    }
    
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/validation/v3/items/{itemId}";
}

타입 검증(가격, 수량에 문자x) -> 바인딩 실패
필드 검증(필수입력, 공백x, 범위 지정) -> 검증 실패
등 입력 오류가 발생한 경우

  • @NotBlank
    문자열에 대해 지정하는 어노테이션으로 null, "", " " 모두 에러로 지정한다

  • @NotBlank
    null 불가 + 빈 문자열 불가
    @NotBlank에서 공백만 뺀

  • @NotNull
    값이 null이면 에러로 지정한다
    모든 타입에 적용이 가능하다
    form에서 데이터가 값이 넘어올때 빈값이면 "" String이 전달되고 spring은 이를 null로 변환한다. 따라서 null값을 지정할 수 있는 참조타입을 지정해야한다

  • @Min(value), @Max(value)
    숫자 최대,최소값 지정

  • @Size(min, max)
    숫자

  • @Validated, BindingResult
    DTO에 대한 Bean Validation을 실행한다
    타입오류같은 이유로 바인딩이 실패하면 Bean Validation이 적용되지않고 bindingResult의 FieldError 객체가 생성되어 값을 보관한다.

Field Error

특정 프로퍼티에 대한 오류
바인딩 실패(typeMisMatch)나 Bean Validation 실패(NotNull, NotBlank...)시 자동 추가
바인딩 실패 시 filed error에 값을 담고 bean validation은 하지않는다
object(어떤 DTO인지)
field(어떤 필드에서 오류가 발생했는지)
rejectedValue(사용자가 입력한 오류 값)
bindingFailure(바인딩 실패인지 검증 실패인지 구분)
codes(메시지코드)

Object Error

if (item.getPrice() != null && item.getQuantity != null) {
	int resultPrice = item.getPrice() * item.getQuantity();
    if (resultPrice < 10000) {
    	bindingResult.reject("totalpriceMin", new Object[]{10000,resultPrice), null);
    }

특정 필드가 아닌 오브젝트 단위의 오류
컨트롤러에서 직접 추가한다
bindingResult.reject(String errorCode, Object[] errorArgs, String defaultMessage);

에러 코드

//application.properties
spring.messages.basename = messages,errors
// src/main/resources/errors.properties
NotBlank.item.itemName = ...
totalPriceMin = ...

BeanValidation에서 사용된 애노테이션(NotNull, NotBlank...), ObjectError에서 정의한 에러코드에서 에러 메시지를 호출한다

FieldError (ex - 에러코드 = NotBlank, 객체명 = item, 필드명 = itemName)
1. NotBlank.item.itemName
2. NotBlank.itemName
3. NotBlank.java.lang.String
4. NotBlank

ObjectError (bindingResult.reject("totalPriceMin"), 객체명 = item)
1. totalPriceMin.item
2. totalPriceMin

폼 객체 분리

사용자 등록 등과 같이 복잡한 값을 받는 경우 별도의 폼 데이터를 받을 객체를 만들어서 전달한다
예를 들어 회원 수정과 등록의 경우 필요한 데이터의 범위가 다르므로 구분하여 객체를 만든다

API

MVC/SSR에서는 @Valid + BindingResult 조합으로 오류가 있더라도 예외가 터지지않고 bindingResult에 담아서 그대로 뷰에 돌려준다

API에서는 JSON 바디 -> DTO 변환과정에서 검증이 실패하면 스프링이 MethodArgumentNotValidException 같은 예외를 던져버림. 즉 BindingResult에 담을 수 없음

따라서 API에는 예외 핸들러(@RestControllerAdvice)를 두고 그 안에서 JSON 응답 형식으로 에러 메시지를 만들어 돌려준다.

0개의 댓글