3 분 소요

 인프런 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술편을 학습하고 정리한 내용 입니다.

Bean Validation - 에러 코드

Bean Validation이 기본적으로 제공하는 오류 메시지를 변경하기 위해선

Bean Validation을 적용하고 bindingResult에 등록된 검증 오류 코드를 보자.

오류 코드가 애노테이션 이름으로 등록된다. 아이템 이름은 @NotBlank를 사용했기 때문에 NotBlank로 시작한다.

마치 typeMismatch랑 비슷하다.

NotBlank라는 오류 코드를 기반으로 MessageCodesResolver를 통해 다양한 메시지 코드가 순서대로 생성된다.

@NotBlank (구체적인 것 부터, 범용성 으로)

  • NotBlank.item.itemName
  • NotBlank.itemName
  • NotBlank.java.lang.String
  • NotBlank

자 이걸 보면 이제 메시지 프로퍼티를 등록하면 되지 않을까?

errors.properties

1
2
3
4
#Bean Validation 추가 
NotBlank={0} 공백X 
Range={0}, {2} ~ {1} 허용 
Max={0}, 최대 {1}

자 내가 원하는 방향으로 변경 다.

{0}은 필드 명이고, {1}, {2}... 는 각 애노테이션 마다 다르다.

BeanValidation 메시지 찾는 순서

  1. 생성된 메시지 코드 순서대로 messageSource에서 메시지 찾기
  2. 애노테이션의 message속성 사용 → @NotBlank(message = "공백X {0}")
  3. 라이브러리가 제공하는 기본 값 사용 → 공백일 수 없습니다.

애노테이션의 message 사용 예

이런 식으로 NotBlank 애노테이션에 message를 넣어줬다.

잘 된다.

그런데 만약에 errors.properties에도 메시지를 지정한다면???

지금 둘 다 메시지가 설정 되어 있다.

메시지 순서에 따라 프로퍼티 먼저 찾고, 그 후에 애노테이션의 message속성을 찾는 걸 확인할 수 있었다!

Bean Validation - 오브젝트 오류

Bean Validation에서 특정 필드(FieldError)가 아닌 해당 오브젝트 관련 오류(ObjectError)는 어떻게 처리할 수 있을까?

다음과 같이 @ScriptAssert()를 사용하면 된다.

그런데 JDK 14 이후 버전 부터는 ScriptAssert에서 javascript를 사용할 수 없다고 한다.

그리고 실제로 사용한다 해도 제약이 많고 복잡하다.

실무에서는 검증 기능이 해당 객체의 범위를 넘어서는 경우도 있어서 그럼 대응이 어렵다.

따라서 오브젝트 오류(글로벌 오류)의 경우 @ScriptAssert를 쓰는 것보다

자바 코드로 직접 작성하는 것이 바람직하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@PostMapping("/add")  
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {  
  
    //특정 필드 예외가 아닌 전체 예외  
    if (item.getPrice() != null && item.getQuantity() != null) {  
        int resultPrice = item.getPrice() * item.getQuantity();  
        if (resultPrice < 10000) {  
            bindingResult.reject("totalPriceMin", new Object[]{10000,  
                    resultPrice}, null);  
        }  
    }  
    // 검증에 실패하면 다시 입력 폼으로 가야함.  
    if (bindingResult.hasErrors()) {  
        log.info("errors: {}", bindingResult);  
        return "validation/v3/addForm";  
    }  
  
    // 성공 로직  
    Item savedItem = itemRepository.save(item);  
    redirectAttributes.addAttribute("itemId", savedItem.getId());  
    redirectAttributes.addAttribute("status", true);  
    return "redirect:/validation/v3/items/{itemId}";  
}

그래서 예전으로 돌아가서 글로벌 에러는 컨트롤러 안에 직접 구현했다. (메서드로 뽑아도 될 듯.)

Bean Validation - 수정에 적용

상품 수정에도 빈 검증을 적용해 보자.

1
2
3
4
5
@PostMapping("/{itemId}/edit")  
public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {  
    itemRepository.update(itemId, item);  
    return "redirect:/validation/v3/items/{itemId}";  
}

원래 메서드 이었고, 이제 바꿔보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@PostMapping("/{itemId}/edit")  
public String edit(@PathVariable Long itemId, @Validated @ModelAttribute Item item, BindingResult bindingResult) {  
  
    //특정 필드 예외가 아닌 전체 예외  
    if (item.getPrice() != null && item.getQuantity() != null) {  
        int resultPrice = item.getPrice() * item.getQuantity();  
        if (resultPrice < 10000) {  
            bindingResult.reject("totalPriceMin", new Object[]{10000,  
                    resultPrice}, null);  
        }  
    }  
  
    if (bindingResult.hasErrors()) {  
        log.info("errors: {}", bindingResult);  
        return "validation/v3/editForm";  
    }  
  
    itemRepository.update(itemId, item);  
    return "redirect:/validation/v3/items/{itemId}";  
}

@Validated을 Item객체에 붙여줘서 검증하도록 했고, 오브젝트 에러는 자바 코드로 직접 넣어 줬다.

그리고 오류를 담을 BindingResult bindingResult도 선언해 놨다.

마지막으로 오류 가 있다면 다시 수정 폼으로 돌아가도록 return "validation/v3/editForm" 라고 작성했다.

이제 뷰에도 오류가 나올 수 있도록 수정하자.

addForm을 잘 따라가면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
...
    <style>  
        .container {  
            max-width: 560px;  
        }  
        .field-error {  
            border-color: #dc3545;  
            color: #dc3545;  
        }  
    </style>  
</head>  
<body>  
  
<div class="container">  
  
    <div class="py-5 text-center">  
        <h2 th:text="#{page.updateItem}">상품 수정</h2>  
    </div>  
  
    <form action="item.html" th:action th:object="${item}" method="post">  
  
        <div th:if="${#fields.hasGlobalErrors()}">  
            <p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">전체 오류 메시지</p>  
        </div>  
  
        <div>  
            <label for="id" th:text="#{label.item.id}">상품 ID</label>  
            <input type="text" id="id" th:field="*{id}" class="form-control" readonly>  
        </div>  
        <div>  
            <label for="itemName" th:text="#{label.item.itemName}">상품명</label>  
            <input type="text" id="itemName" th:field="*{itemName}"  
                   th:errorclass="field-error" class="form-control">  
            <div class="field-error" th:errors="*{itemName}">  
                상품명 오류  
            </div>  
        </div>  
        <div>  
            <label for="price" th:text="#{label.item.price}">가격</label>  
            <input type="text" id="price" th:field="*{price}"  
                   th:errorclass="field-error" class="form-control">  
            <div class="field-error" th:errors="*{price}">  
                가격 오류  
            </div>  
        </div>  
        <div>  
            <label for="quantity" th:text="#{label.item.quantity}">수량</label>  
            <input type="text" id="quantity" th:field="*{quantity}"  
                  th:errorclass="field-error" class="form-control">  
            <div class="field-error" th:errors="*{quantity}">  
                수량 오류  
            </div>  
        </div>
    ...
    

이렇게 작성 해줬다.

메시지가 잘 나온다!

댓글남기기