타입 변환 오류(가격, 수량에 문자x)
검증 오류(필수입력, 공백x, 범위 지정)
글로벌 오류
특정 필드가 아니라 객체 전체 규칙을 위반했을 때 수동으로 추가
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(검증 오류 포함)
-> (상품 등록 폼)
고객에게 다시 상품 등록 폼을 보여주고 어떤 값이 잘못 되었는지 알려주어야함
public FieldError(String objectName, String field, String defaultMessage) {}
public ObjectError(String objectName, String defatultMessage) {}
objectName : @ModelAttribute, 오류가 발생한 객체의 이름 (ex item)
field : 오류가 발생한 필드 (ex itemName)
스프링이 제공하는 검증 오류를 보관하는 객체
BindingResult가 있으면 @ModelAttribute에 바인딩 오류가 발생해도 컨트롤러 호출
BindinResult가 없으면 400오류가 발생하면서 컨트롤러호출x 오류 페이지로 이동
BindingResult는 검증할 대상 바로 다음에 와야한다.
@ModelAttribute Item item, @BindingResult bindingResult...
필드의 타입 등으로 바인딩 실패시 이를 저장할 수가 없다.
FieldError ObjectError는 이러한 값을 담을 수 있다 (생성자가 따로있음)
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 객체가 생성되어 값을 보관한다.
특정 프로퍼티에 대한 오류
바인딩 실패(typeMisMatch)나 Bean Validation 실패(NotNull, NotBlank...)시 자동 추가
바인딩 실패 시 filed error에 값을 담고 bean validation은 하지않는다
object(어떤 DTO인지)
field(어떤 필드에서 오류가 발생했는지)
rejectedValue(사용자가 입력한 오류 값)
bindingFailure(바인딩 실패인지 검증 실패인지 구분)
codes(메시지코드)
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
사용자 등록 등과 같이 복잡한 값을 받는 경우 별도의 폼 데이터를 받을 객체를 만들어서 전달한다
예를 들어 회원 수정과 등록의 경우 필요한 데이터의 범위가 다르므로 구분하여 객체를 만든다
MVC/SSR에서는 @Valid + BindingResult 조합으로 오류가 있더라도 예외가 터지지않고 bindingResult에 담아서 그대로 뷰에 돌려준다
API에서는 JSON 바디 -> DTO 변환과정에서 검증이 실패하면 스프링이 MethodArgumentNotValidException 같은 예외를 던져버림. 즉 BindingResult에 담을 수 없음
따라서 API에는 예외 핸들러(@RestControllerAdvice)를 두고 그 안에서 JSON 응답 형식으로 에러 메시지를 만들어 돌려준다.