검증시 데이터 보존 원리

김민우·2022년 8월 13일
0

잡동사니

목록 보기
4/22

검증1의 ValidationItemControllerV1에서 검증 오류 발생시 기존 데이터가 보존되는 원리를 자세히 알아보자.

컨트롤러 구현부를 보자.

ValidationItemControllerV1의 일부분

@Controller
@RequestMapping("/validation/v1/items")
@RequiredArgsConstructor
public class ValidationItemControllerV1 {

	...
    
    @GetMapping("/add")
    public String addForm(Model model) {
        model.addAttribute("item", new Item());
        return "validation/v1/addForm";
    }

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

        // 검증 오류 결과를 보관하는 객체
        Map<String, String> errors = new HashMap<>();   // key는 오류 발생한 필드, value는 오류 메시지

        // 검증 로직
        if (!StringUtils.hasText(item.getItemName())) {     // itemName에 글자가 없으면 (StringUtils는 스프링꺼 사용)
            errors.put("itemName", "상품 이름은 필수입니다.");
        }

        if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 1000000) {
            errors.put("price", "가격은 1,000원 ~ 1,000,000원 까지 허용합니다.");
        }

        if (item.getQuantity() == null || item.getQuantity() >= 9999) {
            errors.put("quantity", "수량은 최대 9,999개 까지 허용합니다.");
        }

        // 특정 필드가 아닌 복합 룰 검증
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();

            if (resultPrice < 10000) {
                errors.put("globalError", "가격 * 수량은 10,000원 이상이어야 합니다. 현재값 = " + resultPrice);
            }
        }

        // 검증에 실패하면 다시 입력 폼으로
        if (!errors.isEmpty()) {
            log.info("errors = {}", errors);
            model.addAttribute("errors", errors);
            return "validation/v1/addForm";
        }

        // 성공 로직
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/validation/v1/items/{itemId}";
    }

	...
    
}
  • addForm() 메소드를 보면 모델에 Item 빈 객체를 넣어주는 것을 확인할 수 있다.
  • 뷰 템플릿은 해당 Item 객체를 사용하여 렌더링을 한다.

이제, 도메인에 localhost::8080을 입력하여 상품 목록으로 들어가보자.

상품 등록을 누르고 페이지 소스보기(F12)를 눌러 요청 메소드를 확인하면 다음과 같이 GET 메소드인 것을 확인할 수 있다.

컨트롤러에는 분명 http://localhost:8080/validation/v1/items/add을 매핑하는 메소드는 2개가 정의되있다.

근데 어떻게 @PostMapping이 아닌 @GetMapping 부분이 매핑되었을까?

브라우저는 지금과 같이 하나의 url에 대해 여러 가지 요청 매핑이 있으면 기본적으로 GET 메소드로 매핑된 요청을 실행한다.

값을 입력 후 등록 버튼을 누르면 앞서 addForm()에서 모델에 넣어둔 Item 빈 객체에 데이터가 저장될 것이다.

이 상태에서 잘못된 값을 넣고 등록을 눌러보자. 다음과 같이 검증 오류가 발생할 것이다.

그리고 페이지 소스를 보면 요청 메소드가 POST인 것을 확인할 수 있다.

어떻게 메소드가 POST로 변경이 된 것일까? 비밀은 해당 url의 뷰 템플릿인 addForm.html에 있다.

addForm.html 일부분


<div class="container">

    <div class="py-5 text-center">
        <h2 th:text="#{page.addItem}">상품 등록</h2>
    </div>

    <form action="item.html" th:action th:object="${item}" method="post">
      ...
  • method="post"를 확인할 수 있다.
    (해당 뷰 템플릿은 호출이 되고나면 HTML form의 데이터를 POST로 넘기겠다는 뜻이다.)

참고) HTML Form의 method 속성
<form> 태그의 method 속성은 사용자가 입력한 내용을 어떤 방식(GET, POST)으로 넘길 것인지를 지정하는 역할을 한다.

당연히 HTML Form의 메소드 지원은 GET과 POST 밖에 없으므로 속성값도 get, post가 있다.


이제, 이것들을 사용해서 원리를 생각해보자.

입력했던 값들이 이미 등록 폼을 열 때 생기는 Item 빈 객체에 저장이 되있으므로 잘못된 검증 요청이 오면 다시 입력 폼으로 들어갈 때 addForm.html은 이 객체의 필드값들을 렌더링한다.

참고
html 파일들을 보면 전부 item이라는 객체를 모델에서 꺼내와 쓰는데 그 객체가 바로 위에서 언급한 Item 객체이다.

등록 단계에서 검증 오류가 발생하면 항상 POST로 데이터를 넘겨주므로 Item 객체가 초기화될 일이 없다.(addForm()이 불릴일이 없다.) 따라서, 기존에 입력했던 데이터가 유지된다.

참고로, 잘못 입력된 값들은 타임리프가 아예 값을 입력받지 않으므로 유지되지 않는다.

0개의 댓글