오봉이와 함께하는 개발 블로그

스프링 MVC 2 - Form 전송 객체 분리 소개와 개발 본문

BE/Spring

스프링 MVC 2 - Form 전송 객체 분리 소개와 개발

오봉봉이 2022. 8. 25. 00:53
728x90

Form 전송 객체 분리 - 소개

실무에서는 groups를 잘 사용하지 않는다.
그 이유는 등록시 폼에서 전달하는 데이터가 Item도메인 객체와 딱 맞지 않기 때문이다.
지금 사용하는 예제에서는 폼이 전달하는 데이터와 도메인 객체가 딱 맞지만, 실무에서는 도메인 관련 데이터 외에 약관 정보도 추가도 받는 등, 도메인과 관계없는 수 많는 부가 데이터가 넘어온다.
그래서 보통 도메인을 직접 전달받는 것이 아니라 복잡한 폼 데이터를 컨트롤러까지 전달할 별도의 객체를 만들어서 전달한다.
예를 들면 ItemSaveForm을 만들어서 @ModelAttribute로 사용하고, 이것을 통해 컨트롤러에서 폼 데이터를 전달 받아 필요한 데이터를 사용해서 Item을 사용한다.

폼 데이터 전달에 Item 도메인 객체 사용
HTML Form -> Item -> Controller -> Item -> Repository

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

폼 데이터 전달을 위한 별도의 객체 사용
HTML Form -> ItemSaveForm -> Controller -> Item 생성 -> Repository

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

수정의 경우 등록과 수정은 완전히 다른 데이터가 넘어온다.
예를 들면 등록시에는 로그인id, 주민번호 등등을 받을 수 있지만, 수정시에는 이런 부분이 빠진다.
그리고 검증 로직도 많이 달라지기 때문에 별도의 객체로 데이터를 전달받는 것이 좋다.

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

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

이름은 어떻게 지어야 할까

이름은 의미있게 지으면 된다. ItemSave라고 해도 되고, ItemSaveForm, ItemSaveRequest, ItemSaveDto 등으로 사용해도 된다.
중요한 것은 일관성이다.

Form 전송 객체 분리 - 개발

Item 원복

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

ItemSaveForm - ITEM 저장용 폼

@Data
public class ItemSaveForm {
    @NotBlank
    private String itemName;

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

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

ItemUpdateForm - ITEM 수정용 폼

@Data
public class ItemUpdateForm {
    @NotNull
    private Long id;

    @NotBlank
    private String itemName;

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

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

ValidationItemControllerV4

    @PostMapping("/add")
    public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {

        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/addForm";
        }

        // 검증 성공했을 때
        Item item = new Item(form.getItemName(), form.getPrice(), form.getQuantity());
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/validation/v4/items/{itemId}";
    }

    @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(form.getItemName(), form.getPrice(), form.getQuantity());
        itemRepository.update(itemId, itemParam);
        return "redirect:/validation/v4/items/{itemId}";
    }

Item 대신에 ItemSaveform(ItemUpdateForm)을 전달 받는다. 그리고 @Validated로 검증도 수행하고, BindingResult로 검증 결과도 받는다.

주의
@ModelAttribute("item")item이름을 넣어준 부분을 주의하자.
이것을 넣지 않으면 ItemSaveForm의 경우 규칙에 의해 itemSaveForm이라는 이름으로 MVC Model에 담기게 된다.
이렇게 되면 뷰 템플릿에서 접근하는 th:object 이름도 함께 변경해주어야 한다.

출처 : 인프런 김영한 지식공유자님 강의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
728x90
Comments