입력 타입이 잘못되어서 오류가 나는 경우를 처리 해주어야한다.
잘못된 입력으로 검증 오류가 발생하면 오류 화면으로 바로 이동하게 되면, 고객 입장에서는 입력 해놓은 것들이 날라가서 다시 입력하는 것이 비효율적이어서 고객이탈의 원인이 될 수 있다.
따라서 웹 서비스는 폼 입력시 오류가 발생하면, 고객이 입력한 데이터를 유지한 상태로 어떤 오류가 발생했는지 친절하게 알려주어야 한다.
컨트롤러의 중요한 역할중 하나는 HTTP 요청이 정상인지 검증하는 것이다.
클라이언트 검증은 조작할 수 있으므로 보안에 취약하다.
서버만으로 검증하면, 즉각적인 고객 사용성이 부족해진다.
둘을 적절히 섞어서 사용하되, 최종적으로 서버 검증은 필수
API 방식을 사용하면 API 스펙을 잘 정의해서 검증 오류를 API 응답 결과에 잘 남겨주어야 함
서버의 검증 로직이 실패한 경우 고객에게 다시 상품 등록 폼과 기존에 입력된 값을 전달하고 어떤 값을 잘못 입력했는지 알려주어야 한다.

상품입력을 예로 들어보면 아래와 같은 로직으로 구성할 수 있다. 잘못된 입력이 들어오면 다시 등록폼을 리다이렉트 시켜서 입력을 그대로 넘겨주고 입력을 다시 하도록 한다.
@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}";
}
기존에 비어있는 Item객체를 넘겨줬던 이유는 th:object를 사용하기 위해서였다. 추가적으로 이것 말고도 검증에 실패했을 때도 넘어간 데이터가 다시 보이도록 재사용하는데도 사용된다.
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "validation/v1/addForm";
}
검증을 통해서 잘못된 입력을 표시해주기 위해 css를 추가했다.
.field-error {
border-color: #dc3545;
color: #dc3545;
}
글로벌 오류 메시지
<div th:if="${errors?.containsKey('globalError')}">
<p class="field-error" th:text="${errors['globalError']}">전체 오류 메시지</p>
</div>
오류 메시지는 errors 에 내용이 있을 때만 출력하면 된다. 타임리프의 th:if 를 사용하면 조건에 만족할 때만 해당 HTML 태그를 출력할 수 있다.
만약 여기에서 errors 가 null 이라면 어떻게 될까?
생각해보면 등록폼에 진입한 시점에는 errors 가 없다.
따라서 errors.containsKey() 를 호출하는 순간 NullPointerException 이 발생한다.
errors?. 은 errors 가 null 일때 NullPointerException 이 발생하는 대신, null 을 반환하는 문법이다.
th:if 에서 null 은 실패로 처리되므로 오류 메시지가 출력되지 않는다.
필드 오류 처리 - 메시지
<div class="field-error" th:if="${errors?.containsKey('itemName')}" th:text="${errors['itemName']}">
상품명 오류
</div>
필드 오류 처리 - 입력 폼 색상 적용
<input type="text" class="form-control field-error">

만약 검증 오류가 발생하면 입력 폼을 다시 보여준다.
검증 오류들을 고객에게 친절하게 안내해서 다시 입력할 수 있게 한다.
검증 오류가 발생해도 고객이 입력한 데이터가 유지된다.
'Spring' 카테고리의 다른 글
| 오류 메시지 처리(2) (0) | 2023.07.14 |
|---|---|
| 오류 메시지 처리 (0) | 2023.07.14 |
| 웹 애플리케이션에 메시지+국제화 적용 (0) | 2023.07.03 |
| 메시지와 국제화 (1) | 2023.07.03 |
| 타임리프 라디오 버튼 (0) | 2023.06.28 |