250x250
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 서블릿
- objecterror
- fielderror
- 김영한
- JSON
- ocp
- 코드트리조별과제
- 백준
- 테스트코드
- html form
- 코드트리
- 스프링
- 다형성
- 스프링컨테이너
- 코딩테스트
- HttpServletResponse
- 오블완
- 싱글톤
- equals()
- java
- DI
- 오버라이딩
- 프록시
- 참조변수
- 티스토리챌린지
- 추상클래스
- http 메시지 컨버터
- @configuration
- 의존관계
- 인터페이스
Archives
- Today
- Total
minOS
Bean Validation - 오브젝트 오류,수정에 적용,한계 본문
728x90
오브젝트 오류
Bean Validation에서 특정 필드( `FieldError` )가 아닌 해당 오브젝트 관련 오류( `ObjectError` )는 어떻게 처리할 수 있을까?
`@ScriptAssert()` 를 사용하면 된다.
@Data @ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >= 10000") public class Item { //... }
실행해보면 정상 수행되는 것을 확인할 수 있다.
메시지 코드도 다음과 같이 생성된다.
메시지 코드
`ScriptAssert.item`
`ScriptAssert`
그런데 실제 사용해보면 제약이 많고 복잡하다. 그리고 실무에서는 검증 기능이 해당 객체의 범위를 넘어서는 경우들도 종종 등장하는데, 그런 경우 대응이 어렵다. 따라서 오브젝트 오류(글로벌 오류)의 경우 `@ScriptAssert` 을 억지로 사용하는 것 보다는 다음과 같이 오브젝트 오류 관련 부분만 직접 자바 코드로 작성하는 것을 권장한다.
@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 - 수정에 적용
상품 수정에도 빈 검증(Bean Validation)을 적용해보자.
@PostMapping("/{itemId}/edit") public String edit2(@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("error ={}", bindingResult); return "/validation/v3/editForm"; } itemRepository.update(itemId, item); return "redirect:/validation/v3/items/{itemId}"; }
- `edit()` : Item 모델 객체에 `@Validated` 를 추가하자.
- 검증 오류가 발생하면 `editForm` 으로 이동하는 코드 추가
`validation/v3/editForm.html` 변경
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="utf-8"> <link th:href="@{/css/bootstrap.min.css}" href="../css/bootstrap.min.css" rel="stylesheet"> <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> <hr class="my-4"> <div class="row"> <div class="col"> <button class="w-100 btn btn-primary btn-lg" type="submit" th:text="#{button.save}">저장</button> </div> <div class="col"> <button class="w-100 btn btn-secondary btn-lg" onclick="location.href='item.html'" th:onclick="|location.href='@{/validation/v3/items/{itemId}(itemId=${item.id})}'|" type="button" th:text="#{button.cancel}">취소</button> </div> </div> </form> </div> <!-- /container --> </body> </html>
-`.field-error` css 추가
- 글로벌 오류 메시지
- 상품명, 가격, 수량 필드에 검증 기능 추가
Bean Validation - 한계
수정시 검증 요구사항
데이터를 등록할 때와 수정할 때는 요구사항이 다를 수 있다.
등록시 기존 요구사항
타입 검증
- 가격, 수량에 문자가 들어가면 검증 오류 처리
필드 검증- 상품명: 필수, 공백X
- 가격: 1000원 이상, 1백만원 이하
- 수량: 최대 9999특정 필드의 범위를 넘어서는 검증
- 가격 * 수량의 합은 10,000원 이상
수정시 요구사항
1)등록시에는 `quantity` 수량을 최대 9999까지 등록할 수 있지만 수정시에는 수량을 무제한으로 변경할 수 있다.
2)등록시에는 `id` 에 값이 없어도 되지만, 수정시에는 id 값이 필수이다.
수정 요구사항 적용
id` : `@NotNull` 추가@Data public class Item { @NotNull //수정 요구사항 추가 private Long id; @NotBlank private String itemName; @NotNull @Range(min = 1000, max = 1000000) private Integer price; @NotNull //@Max(9999) //수정 요구사항 추가 private Integer quantity; //... }
quantity` : `@Max(9999)` 제거
참고
현재 구조에서는 수정시 `item` 의 `id` 값은 항상 들어있도록 로직이 구성되어 있다. 그래서 검증하지 않아도 된다고 생각할 수 있다. 그런데 HTTP 요청은 언제든지 악의적으로 변경해서 요청할 수 있으므로 서버에서 항상 검증 해야 한다. 예를 들어서 HTTP 요청을 변경해서 `item` 의 `id` 값을 삭제하고 요청할 수도 있다. 따라서 최종 검증 은 서버에서 진행하는 것이 안전한다.
수정을 실행하면정상 동작을 확인할 수 있다.
그런데 수정은 잘 동작하지만 등록에서 문제가 발생한다.
등록시에는 `id` 에 값도 없고, `quantity` 수량 제한 최대 값인 9999도 적용되지 않는 문제가 발생한다.
결과적으로 `item` 은 등록과 수정에서 검증 조건의 충돌이 발생하고, 등록과 수정은 같은 BeanValidation을 적용할 수 없다. 이 문제를 어떻게 해결할 수 있을까?
728x90
'TIL > 김영한의 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술' 카테고리의 다른 글
로그인 - 요구사항, 쿠키 사용 (1) | 2024.09.24 |
---|---|
Bean Validation - groups,Form 전송 객체 분리, HTTP 메시지 컨버터 (2) | 2024.09.18 |
Bean Validation - Bean Validation 애노테이션 적용,스프링 적용,에러 코드 (0) | 2024.09.18 |
Validator 분리 (1) | 2024.09.14 |
오류 코드와 메세지 처리 (7) | 2024.09.13 |