@PostMapping("/add")
public String addItem(
@ModelAttribute Item item,
RedirectAttributes redirectAttributes,
Model model
) {
//-- 검증 로직 --//
// map 으로 검증 오류 결과를 보관
Map<String, String> errors = new HashMap<>();
// 특정 필드 검증 로직
if (!StringUtils.hasText(item.getItemName()))
errors.put("itemName", "상품 이름은 필수입니다.");
if (item.getPrice() == null || item.getPrice() < 1000 || item.getPrice() > 100000)
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()) {
model.addAttribute("errors", errors);
// 검증 실패시 다시 form 으로 이동
return "validation/v1/addForm";
}
//-- 검증 성공 로직 --//
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v1/items/{itemId}";
}
⚠️ 검증 로직이 잘 작동되고있는지 더 자세히 확인하고 싶으면 로그를 사용하면 된다.
<!-- css -->
.field-error {
border-color: #dc3545;
color: #dc3545;
}
<!-- 글로벌 오류 표시 시작-->
<div th:if="${errors?.containsKey('globalError')}">
<p class="field-error"
th:text="${errors['globalError']}"></p>
</div>
<!-- 글로벌 오류 표시 종료-->
th:class
속성에 조건식을 걸어 검증에 실패할경우 CSS 가 달라지도록 설정했다.?
를 붙여줘야하는 이유errors.containsKey
은 null 에 .containsKey
을 했다는 의미가 되므로 500 에러가 발생하게된다.?
은 해당 변수가 null 일때 NPE 를 발생시키지 않고 null 을 반환시켜주는 표현식이다.<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<input type="text"
th:field="*{itemName}"
th:class="${errors?.containsKey('itemName')} ? 'form-control field-error' : 'form-control'"
placeholder="이름을 입력하세요">
<div class="field-error"
th:if="${errors?.containsKey('itemName')}">
[[${errors['itemName']}]]
</div>
th:classappend
를 사용하는 방법도 있다.<input type="text"
th:classappend="${errors?.containsKey('itemName')} ? 'field-error' : _"
class="form-control">
<div th:if="${errors?.containsKey('globalError')}">
<p class="field-error"
th:text="${errors['globalError']}"></p>
</div>
<div>
<label for="itemName" th:text="#{label.item.itemName}">상품명</label>
<input type="text"
th:field="*{itemName}"
th:class="${errors?.containsKey('itemName')} ? 'form-control field-error' : 'form-control'"
placeholder="이름을 입력하세요">
<div class="field-error"
th:if="${errors?.containsKey('itemName')}">
[[${errors['itemName']}]]
</div>
</div>
<div>
<label for="price" th:text="#{label.item.price}">가격</label>
<input type="text"
th:field="*{price}"
th:class="${errors?.containsKey('price')} ? 'form-control field-error' : 'form-control'"
placeholder="가격을 입력하세요">
<div class="field-error"
th:if="${errors?.containsKey('price')}">
[[${errors['price']}]]
</div>
</div>
<div>
<label for="quantity" th:text="#{label.item.quantity}">수량</label>
<input type="text"
th:field="*{quantity}"
th:class="${errors?.containsKey('quantity')} ? 'form-control field-error' : 'form-control'"
placeholder="수량을 입력하세요">
<div class="field-error"
th:if="${errors?.containsKey('quantity')}">
[[${errors['quantity']}]]
</div>
</div>