본문 바로가기

Springあるある

BindingResult의 동작

@PostMapping("/add")
public String addItemV2(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {

    /**
     * bindingFailure=false : 클라이언트에서 넘어오는 매개변수와 item의 바인딩이 실패해도 binding 실패라고 보지 않겠다.
     * 검증 로직에 걸렸을 떄에 FieldError를 생성하겠다는 뜻!!
     */
    // 검증 로직
    if(!StringUtils.hasText(item.getItemName()))
        bindingResult.addError(new FieldError("item","itemName",item.getItemName(),
                false,null,null,"상품명은 필수값입니다"));



    if(item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000)
        bindingResult.addError(new FieldError("item","price",item.getPrice(),
                true,null,null,"가격은 1000원 이상 1000000이하 입니다."));



    if(item.getQuantity() == null || item.getQuantity() >= 9999)
        bindingResult.addError(new FieldError("item","quantity",item.getQuantity(),
                true,null,null,"수량은 9,999개 이하입니다"));


    if(item.getPrice()!=null && item.getQuantity() != null){

        int resultPrice = item.getPrice() * item.getQuantity();
        if(resultPrice < 10000){

            bindingResult.addError(new ObjectError("item",null,null,"가격*수량의 합은 10,000원 이어야 한다. 현재 값 ="+resultPrice + "원"));

        }
        
        // 이하 생략
    }

 

 

@Data
public class Item {

    private Long id;
    private String itemName;
    private Integer price;
    private Integer quantity;

    public Item() {
    }

    public Item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
}

 

스프링은 Request HTTP 메시지로 넘어온 데이터를 바인딩하기도 하지만 [타입 오류] 체크도 같이 해준다.

이때 스프링은 내부적으로 자동으로 아래와 같은 FieldError 객체를 생성한다. 

new FieldError("item","price",item.getPrice(),
				//bindingFailure 인수 = true로 설정됨
                true,null,null,"가격은 1000원 이상 1000000이하 입니다.")

고로, 예를 들어 사용자가 price="aaaa"를 입력해서 타입 오류가 나면 컨트롤러 호출 이전 단계에서 자동으로 해당 오류 

를 담아서 컨트롤러에 반환하여(정확히는 NumberException과 같은 타입 오류 예외 메시지를 담아 준다)

컨트롤러를 실행한다.

만약 컨트롤로 실행 시 Price에 대한 [검증 로직 오류]가 발생하면 이건 [타입 오류]에 의한 바인딩 실패(bindingFailure)

가 아니므로, 새로운 Field 객체를 아래와 같이 생성하여 bindinFailure 인수에 false로 설정을 해주면 된다. 

(즉, 타입 오류와 검증 로직 오류를 철저히 분리하고 있다고 생각하면 된다)

 

if(item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000)
    bindingResult.addError(new FieldError("item","price",item.getPrice(),
            false,null,null,"가격은 1000원 이상 1000000이하 입니다."));

 

이때 타임리프는 th:field=&{item.price}에 대해 타입 오류와 검증 오류, 이 2가지 오류가 있다고 판단하여 렌더링한다.

(th:field는 해당 필드에 오류가 발생하면, BindingResult 객체에서 데이터를 꺼내서 사용한다. 만약 정상 상황이면 당연히 Model 객체에 담긴 데이터를 꺼내 사용)