Back-End/Spring

[Spring MVC2][검증2(Validation)] - Form 전송 객체 분리

얄루몬 2022. 5. 5. 20:23

💻본 포스팅은 '스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 김영한'님의 강의를 듣고 작성되었습니다.

https://inf.run/vQHp

 

스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 인프런 | 강의

웹 애플리케이션 개발에 필요한 모든 웹 기술을 기초부터 이해하고, 완성할 수 있습니다. MVC 2편에서는 MVC 1편의 핵심 원리와 구조 위에 실무 웹 개발에 필요한 모든 활용 기술들을 학습할 수 있

www.inflearn.com


[groups를 사용하지 않는 이유]

  • 등록시 폼에서 전달하는 데이터가 'Item' 도메인 객체와 딱 맞지 않기 때문이다
  • 수 많은 부가적인 데이터(=상품과 관련 없는)가 넘어오기 때문에 복잡한 폼의 컨트롤러까지 전달할 별도의 객체를 만들어 전달한다.
  • 이것을 사용해 컨트롤러에서 폼 데이터를 전달 받고 이후 컨트롤러에서 필요한 데이터를 사용해서 Item을 생성한다.

 

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

  • 장점
    • 심플하게 중간과정 없이 도메인 객체를 컨트롤러, 레포지토리까지 직접 전달해준다.
  • 단점
    • 매우 간단해서 간단하게 개발하는 경우에만 사용할 수 있고 수정시 검증이 중복될 수 있는 문제가 있어 groups를 사용해야 한다.

 

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

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

[달라지는 폼 데이터?]

  • 달라지는 폼 데이터에 관해서 간단한 예시로 회원가입(등록) 회원수정(수정)의 경우를 생각해본다면 쉽다.
  • 등록 시에는 주민등록, id 등을 입력 받지만 수정의 경우엔 주민등록 번호나 id를 다시 재입력 받을 필요가 없다.(수정이 불가한 항목일 경우도 있다.)
  • 그렇기에 검증 로직도 달라지고 데이터 범위도 달라지게 되는 것이다.
  • 다시 말해서 등록과 수정은 같아 보이지만 완전히 다른 데이터가 넘어온다고 봐야 한다.
  • 그래서 결과적으로는 ItemUpdateForm이라는 별도의 객체로 데이터를 따로 전달 받는 것이 좋다. (등록 시 폼 데이터와 수정 시 넘어오는 폼 데이터를 서로 다른 객체로 받는 것을 의미한다.)
  • 또한 수 많은 부가적인(약관에 대한 정보 등등..) 정보도 폼 데이터를 통해 넘어오기 때문에 폼 데이터를 하나의 객체로 사용하는 groups는 잘 사용하지 않는 것이다.

 

[Item 객체 수정]

[기존 Item 코드]

package hello.itemservice.domain.item;

import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.ScriptAssert;

import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
//@ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >= 10000")
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;

    public Item() {
    }

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

기존 검증을 위해서 사용했던 부분을 싹 - 다 지워줘도 된다.

 

[수정 후]

package hello.itemservice.domain.item;

import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.ScriptAssert;

import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

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

 

[폼 데이터의 분리 작업]

[등록 폼 데이터] 

package hello.itemservice.web.validation.form;

import lombok.Data;
import org.hibernate.validator.constraints.Range;

import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class ItemSaveForm {

    @NotBlank
    private String itemName;

    @NotNull
    @Range(min = 1000, max = 1000000)
    private Integer price;

    @NotNull
    @Max(value = 9999)
    private Integer quantity;

}

[수정 폼 데이터]

package hello.itemservice.web.validation.form;

import lombok.Data;
import org.hibernate.validator.constraints.Range;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class ItemUpdateForm {

    @NotNull
    private Long id;

    @NotBlank
    private String itemName;

    @NotNull
    @Range(min = 1000, max = 1000000)
    private Integer price;

    // 수정에서는 수량은 자유롭게 변경할 수 있다.
    private Integer quantity;

}

[컨트롤러]

    @PostMapping("/{itemId}/edit")
    public String edit(@PathVariable Long itemId, @Validated @ModelAttribute("item") ItemUpdateForm form, BindingResult bindingResult) {
        //특정 필드가 아닌 복합 룰 검증
        if (form.getPrice() != null && form.getQuantity() != null) {
            int resultPrice = form.getPrice() * form.getQuantity();
            if (resultPrice < 10000) {
                bindingResult.reject("totalPriceMin", new Object[]{10000,
                        resultPrice}, null);
            }
        }

        //검증에 실패하게 될 때 다시 입력 폼으로 간다.
        if (bindingResult.hasErrors()) {
            log.info("errors={}", bindingResult);
            return "validation/v4/editForm";
        }

        Item itemParam = new Item();
        itemParam.setItemName(form.getItemName());
        itemParam.setPrice(form.getPrice());
        itemParam.setQuantity(form.getQuantity());


        itemRepository.update(itemId, itemParam);
        return "redirect:/validation/v4/items/{itemId}";
    }

 

  • 컨트롤러의 경우 Item 대신 수정 폼, 등록 폼에 맞게 폼 데이터 객체를 넘겨준다.
  • 이때 추가적으로 폼 데이터를 Item 객체로 변환해주는 작업을 해야 한다.

[폼 객체를 Item으로 변환]

Item itemParam = new Item();
itemParam.setItemName(form.getItemName());
itemParam.setPrice(form.getPrice());
itemParam.setQuantity(form.getQuantity());