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
- 인프런
- Exception
- 그리디
- Android
- 백준
- Thymeleaf
- springdatajpa
- 스프링 핵심 기능
- 김영한
- QueryDSL
- pointcut
- jpa
- 스프링 핵심 원리
- spring
- JDBC
- db
- transaction
- Servlet
- 자바
- SpringBoot
- java
- 스프링
- http
- JPQL
- AOP
- Greedy
- Spring Boot
- Proxy
- kotlin
- 알고리즘
Archives
- Today
- Total
개발자되기 프로젝트
검증 직접 처리, validation 본문
1. 상품 저장 성공 시나리오
2. 상품 저장 실패 시나리오
- 이런 저런 이유로 data가 누락되거나 잘못된 data가 넘어오는 경우 검증에 실패해야함.
- 이렇게 검증에 실패한 경우 고객에게 다시 상품 등록 폼을보여주고,
- 어떤 값을 잘못 입력했는지 알려줘야함.
- 검증에 실패할 경우 Model에 data를 담아서 상품등록 폼에 다시 전달함.
- Thymeleaf는 넘어온 data를 가지고 랜더링을 다시함.
3. 검증로직 추가 : addItem.
- 검증시 오류가 발생하면 errors 에 담아둔다.
- 이때 어떤 필드에서 오류가 발생했는지 구분하기 위해 오류가 발생한 필드명을 key 로 사용한다.
- 이후 뷰에서 이 데이터를 사용해서 오류 메시지를 출력
@PostMapping("/add")
public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes, Model model) {
//검증 오류 결과를 보관
Map<String, String> errors= new HashMap<>();
//검증 로직
if(!StringUtils.hasText(item.getItemName())){
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}";
}
- 확인해보자.
- 상품 명은 비우고, 가격을 1000원 밑으로 해보자.
- 검증이 의도한대로 잘 동작한다.
errors = {itemName=상품 이름을 필수 입니다., price=가격은 1,000 ~ 1,000,000까지 허용합니당.}
- 어 근데? 검증 logic fail시 입력된 정보를 따로 model에 담은 적이없는데??
-
@PostMapping("/add") public String addItem(@ModelAttribute Item item, RedirectAttributes redirectAttributes, Model model)
- @ModelAttribute Item item은 model.addAttribute("item", item) 기능도 제공한다.
- 따라서 등록 폼에서 작성한 내용이 controller로 넘어오고 model에 담기기 때문에
- 검증 fail시 해당 값을 재사용가능
-
4. addForm.html 수정
- globalError 발생 시 view에서 나타낼 수 있도록 html 수정
- errors?.containsKey('globalError')
- errors? -> errors가 null이면 무시하는 로직.
- Map사용하기 > erros['key']
<div th:if="${errors?.containsKey('globalError')}">
<p class="field-error" th:text="${errors['globalError']}">global error message</p>
</div>
- 랜더링 결과
<div>
<p class="field-error">가격 * 수량은 10,000원 이상이어야 합니다. 현재 값 = 10</p>
</div>
- validation fail 시 테두리도 빨간색으로.
<style>
.container {
max-width: 560px;
}
.field-error {
border-color: #dc3545;
color: #dc3545;
}
</style>
- th:class, 삼항 연산자를 활용
- error가 있으면 input box의 클래스를 form-control, field-error로 지정.
- error가 없으면 form-control로 지정.
<div>
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<input type="text" id="itemName" th:field="*{itemName}"
th:class="${errors?.containsKey('itemName')} ? 'form-control field-error' : 'form-control'"
class="form-control" placeholder="이름을 입력하세요">
<div class="field-error" th:if="${errors?.containsKey('itemName')}" th:text="${errors['itemName']}">
상품명 오류
</div>
</div>
- 랜더링 결과
<div>
<label for="itemName">상품명</label>
<input type="text" id="itemName" class="form-control field-error" placeholder="이름을 입력하세요" name="itemName" value="">
<div class="field-error">상품 이름을 필수 입니다.</div>
</div>
- 나머지 항목도 동일하게 적용
5. Safe Navigation Operator
- 만약 여기에서 errors 가 null 이라면 어떻게 될까?
- 생각해보면 등록폼에 진입한 시점에는 errors 가 없다.
- 하지만 addForm.html에서는 error는 사용한다.
- 따라서 errors.containsKey() 를 호출하는 순간 NullPointerException 이 발생한다.
- errors?. 은 errors 가 null 일때 NullPointerException 이 발생하는 대신, null 을 반환.
- th:if 에서 null 은 실패로 처리되므로 오류 메시지가 출력되지 않는다.
이것은 스프링의 SpringEL이 제공하는 문법이다. 자세한 내용은 다음을 참고
6. 필드 오류 처리, classAppend
- errors가 있으면 class에 field-error를 추가하고
- 없으면 '_'(no-operation)을 사용해서 아무것도 하지 않음
<input type="text" th:classappend="${errors?.containsKey('itemName')} ? 'field-error' : _"
class="form-control">
7. 정리
- 만약 검증 오류가 발생하면 입력 폼을 다시 보여준다.
- 검증 오류들을 고객에게 친절하게 안내해서 다시 입력할 수 있게 한다.
- 검증 오류가 발생해도 고객이 입력한 데이터가 유지된다.
8. 남은 문제점
- 뷰 템플릿에서 중복 처리가 많다. 뭔가 비슷하다.
- 타입 오류 처리가 안된다.
- Item 의 price , quantity 같은 숫자 필드는 타입이 Integer 이므로 문자 타입으로 설정하는 것이 불가능하다.
- 숫자 타입에 문자가 들어오면 오류가 발생한다.
- 그런데 이러한 오류는 스프링MVC에서 컨트롤러에 진입하기도 전에 예외가 발생하기 때문에,
- 컨트롤러가 호출되지도 않고, 400 예외가 발생하면서 오류 페이지를 띄워준다.
- Item 의 price 에 문자를 입력하는 것 처럼 타입 오류가 발생해도
- 고객이 입력한 문자를 화면에 남겨야 한다.
- 만약 컨트롤러가 호출된다고 가정해도 Item 의 price 는 Integer 이므로 문자를 보관할 수가 없다.
- 결국 문자는 바인딩이 불가능하므로 고객이 입력한 문자가 사라지게 되고,
- 고객은 본인이 어떤 내용을 입력해서 오류가 발생했는지 이해하기 어렵다.
- 결국 고객이 입력한 값도 어딘가에 별도로 관리가 되어야 한다.
9. GitHub : Validation-V1
'인프런 > [인프런] 스프링 MVC 2' 카테고리의 다른 글
BindingResult2 (0) | 2021.09.24 |
---|---|
BindingResult1 (0) | 2021.09.24 |
검증 요구사항. (0) | 2021.09.24 |
WebApplication 국제화 적용 (0) | 2021.09.24 |
WebApplication에 메시지 적용 (0) | 2021.09.24 |
Comments