12. 검증 (Validation)

πŸ€” μ‚¬μš©μžκ°€ μ›ΉνŽ˜μ΄μ§€μ— μƒν’ˆμ„ λ“±λ‘ν•˜κ³  μˆ˜μ •ν•  수 μžˆλŠ” <μƒν’ˆ 관리="" μ‹œμŠ€ν…œ="">을 κ°œλ°œν–ˆλ‹€κ³  κ°€μ •ν•΄λ³΄μž. 여기에 λ‹€μ–‘ν•œ μš”κ΅¬μ‚¬ν•­μ΄ 생겼닀.

  1. νƒ€μž… 검증
  • 가격, μˆ˜λŸ‰μ— λ¬Έμžκ°€ λ“€μ–΄κ°€λ©΄ 검증 였λ₯˜ 처리
  1. ν•„λ“œ 검증
  • μƒν’ˆλͺ… : ν•„μˆ˜, 곡백 X
  • 가격 : 1,000원 이상 ~ 1,000,000원 μ΄ν•˜
  • μˆ˜λŸ‰ : μ΅œλŒ€ 9,999κ°œκΉŒμ§€ 등둝 κ°€λŠ₯
  1. νŠΉμ • ν•„λ“œμ˜ λ²”μœ„λ₯Ό λ„˜μ–΄μ„œλŠ” 검증.
  • 가격 * μˆ˜λŸ‰ = 10,000원 이상

쒋은 μ›Ή μ„œλΉ„μŠ€λŠ” μ΄λŸ¬ν•œ 였λ₯˜κ°€ λ°œμƒν•˜λ©΄ 였λ₯˜κ°€ λ°œμƒν•œ 값을 μœ μ§€ν•œ μƒνƒœλ‘œ μ–΄λ–€ 였λ₯˜κ°€ λ°œμƒν–ˆλŠ”μ§€ μ‚¬μš©μžμ—κ²Œ 친절히 μ•Œλ €μ£Όμ–΄μ•Ό ν•œλ‹€.

좜처: κΉ€μ˜ν•œμ˜ μŠ€ν”„λ§MVC (μΈν”„λŸ°)

자, 이제 κ°œλ°œν•΄λ³΄μž πŸ™‹πŸ»β€β™€οΈ

1️⃣ Β  BindingResult

  • μŠ€ν”„λ§μ΄ μ œκ³΅ν•˜λŠ” κΈ°λŠ₯이닀.
  • 검증 였λ₯˜λ₯Ό λ³΄κ΄€ν•˜λŠ” 객체둜, 였λ₯˜κ°€ λ°œμƒν•˜λ©΄ 였λ₯˜λ‚΄μš©μ„ μ €μž₯ν•΄μ€€λ‹€.
  • BindingResult κ°€ 있으면 @ModelAttribute 에 데이터 바인딩 μ‹œ 였λ₯˜κ°€ μžˆμ–΄λ„ 컨트둀러λ₯Ό ν˜ΈμΆœν•΄μ€€λ‹€.
  • λ‹€λ§Œ, @RequestBody 둜 데이터 바인딩을 ν•˜λŠ” JSON λ°μ΄ν„°μ˜ 경우 νƒ€μž… μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ λͺ¨λΈ 객체에 데이터가 담기지 μ•Šκ³ , 컨트둀러λ₯Ό ν˜ΈμΆœν•  수 μ—†κ²Œ λœλ‹€. 이 κ²½μš°λŠ” μ˜ˆμ™Έ 처리둜 였λ₯˜ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ν•΄μ•Ό ν•˜λŠ”λ° 이것은 λ‹€μŒ κ°•μ˜μ—μ„œ μžμ„Ένžˆ μ•Œμ•„λ³΄μž.

2️⃣ Β  BindingResult - rejectValue(), reject() λ©”μ„œλ“œ

  • BindingResult λŠ” rejectValue(), reject() λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•œλ‹€. ν•„λ“œλͺ…κ³Ό μ—λŸ¬μ½”λ“œλ₯Ό νŒŒλΌλ―Έν„°λ‘œ λ„˜κΈ°λ©΄, 그에 ν•΄λ‹Ήν•˜λŠ” 였λ₯˜ λ©”μ‹œμ§€λ₯Ό λΈŒλΌμš°μ €μ— 좜λ ₯ν•  수 μžˆλ‹€.
  • rejectValue() : FieldError λ₯Ό κ²€μ¦ν•˜λŠ” λ©”μ„œλ“œ.
  • reject() : ObjectError λ₯Ό κ²€μ¦ν•˜λŠ” λ©”μ„œλ“œ.
/* 검증 둜직 예제 */

if (!StringUtils.hasText(item.getItemName())) {
   //ItemName 이 곡백이라면,
   bindingResult.rejectValue("itemName", "required");
   //itemName ν•„λ“œμ˜ required μ—λŸ¬ μ½”λ“œλ₯Ό bindingResult에 λ‹΄λŠ”λ‹€.
}

//bindingResult 에 μ—λŸ¬κ°€ λ‹΄κ²¨μžˆλ‹€λ©΄,
if (bindingResult.hasErrors()) {
   //μ‚¬μš©μžκ°€ μž…λ ₯ν•œ 폼으둜 λ˜λŒλ €λ³΄λ‚Έλ‹€.
   return "validation/v2/addForm";
}

3️⃣ Β  errors.properties

  • 였λ₯˜ λ©”μ‹œμ§€λ₯Ό κ΅¬λΆ„ν•˜κΈ° μ‰½κ²Œ λ³„λ„μ˜ 파일둜 κ΄€λ¦¬ν•˜μž.
  • 였λ₯˜ μ½”λ“œλ₯Ό λ§Œλ“€ λ•ŒλŠ” μžμ„Ένžˆ λ§Œλ“€ 수 있고, λ‹¨μˆœν•˜κ²Œ λ§Œλ“€ μˆ˜λ„ μžˆλ‹€.
#level1 - μžμ„Ένžˆ λ§Œλ“€κΈ° (μ—λŸ¬λͺ….objectλͺ….fieldλͺ…)
required.item.itemName=μƒν’ˆ 이름은 ν•„μˆ˜ μž…λ‹ˆλ‹€.

#level2 - λ‹¨μˆœν•˜κ²Œ λ§Œλ“€κΈ°
required=ν•„μˆ˜ κ°’ μž…λ‹ˆλ‹€.
  • λ‹¨μˆœν•˜κ²Œ λ§Œλ“€λ©΄ λ²”μš©μ„±μ΄ μ’‹μ•„μ„œ μ—¬κΈ°μ €κΈ°μ„œ μ‚¬μš©ν•  수 μžˆμ§€λ§Œ λ©”μ‹œμ§€λ₯Ό μ„Έλ°€ν•˜κ²Œ λ§Œλ“€ 수 μ—†κ³ , λ°˜λŒ€λ‘œ λ„ˆλ¬΄ μžμ„Έν•˜κ²Œ λ§Œλ“€λ©΄ λ²”μš©μ„±μ΄ 떨어진닀.
  • μŠ€ν”„λ§μ€ μ„Έλ°€ν•˜κ²Œ μž‘μ„±λœ μ—λŸ¬ μ½”λ“œ(level1)λ₯Ό μš°μ„ μœΌλ‘œ μ μš©ν•˜κΈ° λ•Œλ¬Έμ— 상황에 맞게 μž‘μ„±ν•΄λ‘λ©΄ λœλ‹€.

4️⃣ Β  검증 였λ₯˜λ₯Ό μ μš©ν•˜λŠ” 3가지 방법

1.  μŠ€ν”„λ§μ΄ FieldError λ₯Ό μƒμ„±ν•΄μ„œ BindingResult 에 직접 λ„£μ–΄μ€€λ‹€.
2.  κ°œλ°œμžκ°€ 직접 λ„£μ–΄μ€€λ‹€.
3.  Validator λ₯Ό λΆ„λ¦¬ν•œλ‹€.
  • 검증 μš”κ΅¬μ‚¬ν•­ 쀑 <1번. νƒ€μž… 검증>μ—μ„œ 가격, μˆ˜λŸ‰μ— λ¬Έμžμ—΄μ„ λ„£μœΌλ©΄ @ModelAttribute λ₯Ό 톡해 Item 객체에 데이터가 μ£Όμž…λ˜μ§€ μ•ŠλŠ”λ‹€. μ΄λŸ¬ν•œ κ²½μš°λŠ” typeMismatch둜 μŠ€ν”„λ§μ΄ μžλ™μœΌλ‘œ FieldError λ₯Ό 생성해쀀닀.

  • 검증 μš”κ΅¬μ‚¬ν•­ 쀑 <3번. νŠΉμ • ν•„λ“œμ˜ λ²”μœ„λ₯Ό λ„˜μ–΄μ„œλŠ” 검증> 은 ObjectError 에 ν•΄λ‹Ήν•˜λŠ” κΈ€λ‘œλ²Œ 였λ₯˜λ‘œ, κ°œλ°œμžκ°€ 직접 μžλ°” μ½”λ“œλ‘œ μ—λŸ¬μƒν™©μ„ μ •μ˜ν•œ 이후 직접 bindingResult 에 μ—λŸ¬μ½”λ“œλ₯Ό λ‹΄λŠ”λ‹€.

/* κ°œλ°œμžκ°€ 직접 μ •μ˜ν•˜λŠ” 검증 둜직 */
if (item.getPrice() != null && item.getQuantity() != null) {
    int resultPrice = item.getPrice() * item.getQuantity();
    if (resultPrice < 10000) {
        bindingResult.reject("totalPriceMin", new Object[]{1000, resultPrice}, null);
    }
}
  • ν•˜μ§€λ§Œ μ΄λŸ¬ν•œ 검증 둜직이 β€˜μ»¨νŠΈλ‘€λŸ¬β€™μ— λ“€μ–΄κ°€ μžˆλŠ” 것이 λ¬Έμ œκ°€ 될 수 μžˆλ‹€. 컨트둀러의 역할이 컀지고 μœ μ§€λ³΄μˆ˜μ„±μ΄ 떨어지기 λ•Œλ¬Έμ— Validator λΌλŠ” 클래슀λ₯Ό λ³„λ„λ‘œ λ§Œλ“€μ–΄μ„œ λ‘œμ§μ„ λΆ„λ¦¬ν•˜λŠ” 것이 μ’‹λ‹€.
public String addItemV5(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {

    //검증 λ‘œμ§μ„ μˆ˜ν–‰ (νƒ€κ²Ÿ, BindingResult λ₯Ό νŒŒλΌλ―Έν„°λ‘œ λ„˜κΈ΄λ‹€)
    itemValidator.validate(item, bindingResult);

    //검증에 μ‹€νŒ¨ν•˜λ©΄ λ‹€μ‹œ μž…λ ₯ 폼으둜
    if (bindingResult.hasErrors()) {
        log.info("errors = {}", bindingResult);
        return "validation/v2/addForm";
    }

    //였λ₯˜κ°€ μ—†λ‹€λ©΄ 성곡 둜직 μˆ˜ν–‰
    Item savedItem = itemRepository.save(item);
    redirectAttributes.addAttribute("itemId", savedItem.getId());
    redirectAttributes.addAttribute("status", true);
    return "redirect:/validation/v2/items/{itemId}";
}