Amenable
Amenable's Blog
Amenable
  • λΆ„λ₯˜ 전체보기 (189)
    • πŸ“‚ JAVA (87)
      • μ΄νŽ™ν‹°λΈŒ μžλ°” (65)
      • μ£Όμš” κ°œλ… (22)
    • πŸ“‚ 개발 μ„œμ  (22)
      • μ‹€μš©μ£Όμ˜ ν”„λ‘œκ·Έλž˜λ¨Έ (1)
      • 객체지ν–₯의 사싀과 μ˜€ν•΄ (2)
      • 클린 μ½”λ“œ (8)
      • ν•¨κ»˜ 자라기 (1)
      • 그림으둜 λ°°μš°λŠ” HTTP&Network Basic (10)
    • πŸ“‚ λ°μ΄ν„°λ² μ΄μŠ€ (8)
      • κ°œλ… (8)
      • λ¬Έμ œν’€μ΄ (0)
    • πŸ“‚ λ„€νŠΈμ›Œν¬ (14)
      • κ°œλ… (6)
      • 성곡과 μ‹€νŒ¨λ₯Ό κ²°μ •ν•˜λŠ” 1%의 λ„€νŠΈμ›Œν¬ 원리 (8)
    • πŸ“‚ μŠ€ν”„λ§ (13)
      • κΈ°λ³Έ κ°œλ… (13)
    • πŸ“‚ WEB (5)
    • πŸ“‚ 자료ꡬ쑰 (12)
      • κ°œλ… (2)
      • μ •λ ¬ (8)
      • 트리 (2)
    • πŸ“‚ μ•Œκ³ λ¦¬μ¦˜ (10)
      • μ΅œμ†Œμ‹ μž₯트리 (2)
      • μ΅œλ‹¨ 경둜 (2)
      • λ¬Έμžμ—΄ (2)
      • ETC (4)
    • πŸ“‚ μ•Œκ³ λ¦¬μ¦˜_λ¬Έμ œν’€μ΄ (4)
      • BOJ_λ°±μ€€ (4)
    • πŸ“‚ ν”„λ‘œκ·Έλž˜λ° (3)
    • πŸ“‚ DevOps (2)
      • 배포 (2)
    • πŸ“‚ ν›„κΈ° (8)
      • μš°μ•„ν•œ ν…Œν¬μ½”μŠ€(ν”„λ¦¬μ½”μŠ€) (4)
      • 2023λ…„ (3)
      • 2024λ…„ (1)
    • πŸ“‚ 회고 (1)
      • 2023λ…„ (1)

λΈ”λ‘œκ·Έ 메뉴

  • πŸš€ GitHub

ν‹°μŠ€ν† λ¦¬

hELLO Β· Designed By μ •μƒμš°.
Amenable

Amenable's Blog

[Spring] 검증(Validation) - BindingResult, FieldError, ObjectError
πŸ“‚ μŠ€ν”„λ§/κΈ°λ³Έ κ°œλ…

[Spring] 검증(Validation) - BindingResult, FieldError, ObjectError

2023. 5. 1. 23:05

  이번 글을 톡해 BindingResultλ₯Ό ν™œμš©ν•œ 검증(Validation)을 μ•Œμ•„λ³΄κ³ μž ν•œλ‹€. 

κΈ€μ˜ μˆœμ„œλŠ”

  1. μ„œλ²„μ—μ„œ 검증이 ν•„μš”ν•œ 이유
    μ™œ μ„œλ²„μ—μ„œ 검증을 μΆ”κ°€μ μœΌλ‘œ ν•΄μ•Ό ν•˜λŠ”κ°€?
  2. BindingResult
    BindingResultλ₯Ό ν™œμš©ν•˜μ—¬ 검증을 μˆ˜ν–‰ν•˜λŠ” 방법
  3. μ‚¬μš©μžμ˜ μž…λ ₯ κ°’ μœ μ§€ 방법
    바인딩을 ν•˜μ§€ λͺ»ν•œ μƒν™©μ—μ„œλ„ μ‚¬μš©μžμ˜ μž…λ ₯ 값을 μ €μž₯ν•˜λŠ” 방법

으둜 μ§„ν–‰ν•˜κ² λ‹€.

 

1. μ„œλ²„μ—μ„œ 검증이 ν•„μš”ν•œ 이유 🧲

  컨트둀러의 μ€‘μš”ν•œ μ—­ν•  쀑 ν•˜λ‚˜λŠ” HTTP μš”μ²­μ΄ 정상인지 κ²€μ¦ν•˜λŠ” 것이닀.

  κ·ΈλŸ¬λ‚˜ ν΄λΌμ΄μ–ΈνŠΈμ—μ„œ 검증을 ν•˜κ³  μ„œλ²„μ—μ„œλŠ” κ²€μ¦λœ 값을 κ·Έλƒ₯ μ“°λ©΄ λ˜λŠ” κ±° μ•„λ‹Œκ°€λΌκ³  생각할 수 μžˆλ‹€. ν•˜μ§€λ§Œ μ•„λž˜μ™€ κ°™μ€ μ΄μœ λ‘œ μΈν•΄μ„œ μ„œλ²„μ—μ„œλ„ κ²€μ¦μ„ μˆ˜ν–‰ν•΄μ•Ό ν•œλ‹€.

  • ν΄λΌμ΄μ–ΈνŠΈ κ²€μ¦μ€ μ‘°μž‘ν•  μˆ˜ μžˆμœΌλ―€λ‘œ λ³΄μ•ˆμ— μ·¨μ•½ν•˜λ‹€
  • μ„œλ²„λ§ŒμœΌλ‘œ κ²€μ¦ν•˜λ©΄, μ¦‰κ°μ μΈ κ³ κ° μ‚¬μš©μ„±μ΄ λΆ€μ‘±ν•΄μ§„λ‹€.
  • ν΄λΌμ΄μ–ΈνŠΈ κ²€μ¦κ³Ό μ„œλ²„ κ²€μ¦μ„ μ„žμ–΄μ„œ μ‚¬μš©ν•˜λ˜, μ΅œμ’…μ μœΌλ‘œ μ„œλ²„ κ²€μ¦μ€ ν•„μˆ˜μ΄λ‹€.
  • API λ°©μ‹μ„ μ‚¬μš©ν•˜λ©΄ API μŠ€νŽ™μ„ μž˜ μ •μ˜ν•΄μ„œ κ²€μ¦ μ˜€λ₯˜λ‘œ API μ‘λ‹΅ κ²°κ³Όμ— μž˜ λ‚¨κ²¨μ€˜μ•Ό ν•œλ‹€.

 

2. BindingResult πŸ”—

πŸ“• 1. κ°œλ…

  μŠ€ν”„λ§μ΄ μ œκ³΅ν•˜λŠ” 검증 였λ₯˜ 처리 λ°©λ²•μ˜ 핡심은 BindingResult이닀.

@PostMapping("/add")
public String addItem(@ModelAttribute Item item, BindingResult bindingResult) {
    ...
}

  BindingResultλŠ” μŠ€ν”„λ§μ΄ μ œκ³΅ν•˜λŠ” 검증 였λ₯˜λ₯Ό λ³΄κ΄€ν•˜λŠ” 객체이닀. 검증 였λ₯˜κ°€ λ°œμƒν•˜λ©΄ BindingResult에 λ³΄κ΄€ν•˜λ©΄ λœλ‹€. 그리고 BindingResultλŠ” Model에 μžλ™μœΌλ‘œ ν¬ν•¨λ˜κΈ° λ•Œλ¬Έμ— κ·Έ κ°’을 μ‘닡을 λ°›λŠ” μͺ½μ—μ„œ μ‚¬μš©ν•˜λ©΄ λœλ‹€.

  μ£Όμ˜ν•  점 ν•œ κ°€μ§€λŠ” BindingResult의 μœ„μΉ˜λŠ” 항상 λ°”μΈλ”©λ˜λŠ” 객체 뒀에 와야 ν•œλ‹€λŠ” 것이닀.

πŸ“• 2. μ‚¬μš©

  BindingResult의 μœ λ¬΄μ— λ”°λΌμ„œ 바인딩 μ‹œ νƒ€μž… 였λ₯˜κ°€ λ°œμƒν–ˆμ„ λ•Œμ˜ 진행이 달라진닀.

  • BindingResultκ°€ μ—†λŠ” κ²½μš°
    400 μ˜€λ₯˜κ°€ λ°œμƒν•˜λ©΄μ„œ μ»¨νŠΈλ‘€λŸ¬κ°€ ν˜ΈμΆœλ˜μ§€ μ•Šκ³ , μ˜€λ₯˜ νŽ˜μ΄μ§€λ‘œ μ΄λ™ν•œλ‹€.
  • BindingResultκ°€ μžˆλŠ” κ²½μš°
    였λ₯˜ μ •보(FieldError)λ₯Ό BindingResult에 λ‹΄μ•„μ„œ μ»¨νŠΈλ‘€λŸ¬λ₯Ό μ •상 ν˜ΈμΆœν•œλ‹€.

  BindingResult에 검증 였λ₯˜λ₯Ό μ μš©ν•˜λŠ” κ²ƒμ—λŠ” 3κ°€μ§€ 방법이 μžˆλ‹€.

  1. μœ„μ—μ„œ μ„€λͺ…ν•œ κ²ƒμ²˜λŸΌ λ°”인딩 μžμ²΄κ°€ μ‹€νŒ¨ν•˜λŠ” κ²½μš°μ—λŠ” μŠ€ν”„λ§μ΄ FieldErrorλ₯Ό μƒμ„±ν•΄μ„œ BindingResult에 λ„£μ–΄μ€€λ‹€.
  2. Validatorλ₯Ό μ‚¬μš©ν•œλ‹€. (λ’€μ—μ„œ λ‹€λ£° μ£Όμ œμΈ Bean Validationμ—μ„œ ν™•μΈν•˜μž.)
  3. κ°œλ°œμžκ°€ μ§μ ‘ λ„£μ–΄μ€€λ‹€.
bindingResult.addError(new FieldError("item", "itemName", "μƒν’ˆ 이름은 ν•„μˆ˜μž…λ‹ˆλ‹€."));

πŸ“• 3. μ‹€μ œ 적용

  μ§€κΈˆκΉŒμ§€μ˜ λ‚΄μš©μ„ κ°€μ§€κ³  Item μ˜ˆμ‹œλ₯Ό μ΄μš©ν•œ μ½”λ“œλ₯Ό 확인해 보자. Item classκ°€ λ‹€μŒκ³Ό κ°™μ΄ μ‘΄μž¬ν•˜κ³ , μ•„λž˜μ™€ κ°™μ€ μ˜€λ₯˜λ₯Ό ν™•인할 κ²ƒμ΄λ‹€.

@Data
public class Item {

    private Long id;
    private String itemName;
    private Integer price;
    private Integer quantity;
}
  1. μ•„μ΄ν…œ μ΄λ¦„(itemName)이 μ—†λŠ” κ²½μš°
  2. 가격(itemPrice)의 λ²”μœ„κ°€ 1,000 ~ 1,000,000이 μ•„λ‹Œ κ²½μš°
  3. 개수(quantity)κ°€ μ΅œλŒ€ 9,999κ°œκ°€ μ•„λ‹Œ κ²½μš°
  4. μ΅œμ’… κ°’(가격(itemPrice) * κ°œμˆ˜(quantity))이 10,000원 λ―Έλ§ŒμΈ κ²½μš°

  이λ₯Ό 검증을 ν™œμš©ν•œ μ½”λ“œλ‘œ ν‘œν˜„ν•˜λ©΄ μ•„λž˜μ™€ κ°™λ‹€.

@Slf4j
@Controller
@RequiredArgsConstructor
public class ValidationController {

    private final ItemRepository itemRepository;

    @PostMapping("/add")
    public String addItem(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
        // 1. μ•„μ΄ν…œ 이름(itemName)이 μ—†λŠ” 경우
        if(!StringUtils.hasText(item.getItemName())){
            bindingResult.addError(new FieldError("item", "itemName", "μƒν’ˆ 이름은 ν•„μˆ˜μž…λ‹ˆλ‹€."));
        }
        
        // 2. 가격(itemPrice)의 λ²”μœ„κ°€ 1,000 ~ 1,000,000이 μ•„λ‹Œ 경우
        if(item.getPrice() == null || item.getPrice() < 1000 | item.getPrice() > 1_000_000){
            bindingResult.addError(new FieldError("item", "price", "가격은 1,000 ~ 1,000,000κΉŒμ§€ ν—ˆμš©ν•©λ‹ˆλ‹€."));
        }

        // 3. 개수(quantity)κ°€ μ΅œλŒ€ 9,999κ°œκ°€ μ•„λ‹Œ 경우
        if(item.getQuantity() == null || item.getQuantity() > 10_000) {
            bindingResult.addError(new FieldError("item", "quantity", "μˆ˜λŸ‰μ€ μ΅œλŒ€ 9,999κΉŒμ§€ ν—ˆμš©ν•©λ‹ˆλ‹€."));
        }

        // νŠΉμ • ν•„λ“œ μ˜ˆμ™Έκ°€ μ•„λ‹Œ 전체 μ˜ˆμ™Έ
        // 4. μ΅œμ’… κ°’(가격(itemPrice) * 개수(quantity))이 10,000원 미만인 경우
        if(item.getPrice() != null && item.getQuantity() != null){
            int resultPrice = item.getPrice() * item.getQuantity();

            if(resultPrice < 10_000) {
                bindingResult.addError(new ObjectError("item", "가격 * μˆ˜λŸ‰μ˜ 합은 10,000원 이상이어야 ν•©λ‹ˆλ‹€. ν˜„μž¬ κ°’ = " + resultPrice));
            }
        }

        if(bindingResult.hasErrors()){
            return "item/addForm"; // λ‹€μ‹œ μƒν’ˆ λ“±λ‘νŽ˜μ΄μ§€λ‘œ 이동
        }

        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/items/{itemId}";
    }
}

 

3. μ˜€λ₯˜ λ°œμƒ μ‹œ μ‚¬μš©μž μž…λ ₯ κ°’ μœ μ§€ λ°©λ²• 🧰

  바인딩이 λ˜λŠ” μ‹œμ μ— 였λ₯˜κ°€ λ°œμƒν•˜λ©΄ λͺ¨λΈ 객체에 μ‚¬μš©μž μž…λ ₯ 값을 μœ μ§€ν•˜κΈ° μ–΄λ ΅λ‹€λŠ” λ¬Έμ œκ°€ μžˆλ‹€. 예λ₯Ό λ“€μ–΄ Integer νƒ€μž…인 price에 λ¬Έμžκ°€ λ“€μ–΄μ˜¨λ‹€λ©΄ κ°’을 λ³΄κ΄€ν•  μˆ˜ μ—†κΈ° λ•Œλ¬Έμ΄λ‹€.

  이런 경우 FieldError와 ObjectError의 μƒμ„±μžλ₯Ό μ΄μš©ν•˜μ—¬ ν•΄κ²°ν•  수 μžˆλ‹€. 기쑴에 μ‚¬μš©ν•œ μƒμ„±μžκ°€ μ•„λ‹Œ rejectedValueλ₯Ό μ‚¬μš©ν•  μˆ˜ μžˆλŠ” μƒμ„±μžλ₯Ό μ‚¬μš©ν•˜μ—¬ rejectedValue에 κ°’을 μ €μž₯ν•˜λ©΄ λœλ‹€. μ•„λž˜λŠ” FieldError와 ObjectError의 μƒμ„±μžλ₯Ό λ‚˜νƒ€λ‚Έ 것이닀.

// rejectedValue X
public FieldError(String objectName, 
                  String field, 
                  String defaultMessage)

// rejectedValue O
public FieldError(String objectName, 
                  String field, 
                  @Nullable Object rejectedValue, 
                  boolean bindingFailure, 
                  @Nullable String[] codes, 
                  @Nullable Object[] arguments, 
                  @Nullable String defaultMessage) 

// rejectedValue X
public ObjectError(String objectName, 
                   @Nullable String defaultMessage) 
    
// rejectedValue O
public ObjectError(String objectName, 
                   @Nullable String[] codes, 
                   @Nullable Object[] arguments, 
                   @Nullable String defaultMessage)

  각각의 νŒŒλΌλ―Έν„°λŠ” λ‹€μŒκ³Ό 같은 의미λ₯Ό κ°€μ§„λ‹€.

  • objectName
    였λ₯˜κ°€ λ°œμƒν•œ κ°μ²΄ μ΄λ¦„
  • field
    였λ₯˜ ν•„λ“œ
  • rejectedValue
    μ‚¬μš©μžκ°€ μž…λ ₯ν•œ κ°’(거절된 κ°’)
  • bindingFailure
    νƒ€μž… μ˜€λ₯˜ κ°™μ€ λ°”인딩 μ‹€νŒ¨μΈμ§€, κ²€μ¦ μ‹€νŒ¨μΈμ§€ κ΅¬λΆ„ κ°’
  • codes
    λ©”μ‹œμ§€ μ½”λ“œ
  • arguments
    λ©”μ‹œμ§€μ—μ„œ μ‚¬μš©ν•˜λŠ” μΈμž
  • defaultMessage
    κΈ°λ³Έ μ˜€λ₯˜ λ©”μ‹œμ§€

  rejectedValueλ₯Ό μ‚¬μš©ν•˜λŠ” μƒμ„±μžλ₯Ό μ΄μš©ν•œλ‹€λ©΄ μ•„λž˜μ™€ 같이 μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆλ‹€.

@Slf4j
@Controller
@RequiredArgsConstructor
public class ValidationController {

    private final ItemRepository itemRepository;

    @PostMapping("/add")
    public String addItem(@ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {

        // 1. μ•„μ΄ν…œ 이름(itemName)이 μ—†λŠ” 경우
        if(!StringUtils.hasText(item.getItemName())){
            bindingResult.addError(
                    new FieldError("item",
                            "itemName",
                            item.getItemName(),
                            false,
                            null,
                            null,
                            "μƒν’ˆ 이름은 ν•„μˆ˜μž…λ‹ˆλ‹€."));
        }

        // 2. 가격(itemPrice)의 λ²”μœ„κ°€ 1,000 ~ 1,000,000이 μ•„λ‹Œ 경우
        if(item.getPrice() == null || item.getPrice() < 1000 | item.getPrice() > 1_000_000){
            bindingResult.addError(new FieldError("item",
                    "price",
                    item.getPrice(),
                    false,
                    null,
                    null,
                    "가격은 1,000 ~ 1,000,000 κΉŒμ§€ ν—ˆμš©ν•©λ‹ˆλ‹€."));
        }

        // 3. 개수(quantity)κ°€ μ΅œλŒ€ 9,999κ°œκ°€ μ•„λ‹Œ 경우
        if(item.getQuantity() == null || item.getQuantity() > 10_000) {
            bindingResult.addError(new FieldError("item",
                    "quantity",
                    item.getQuantity(),
                    false,
                    null,
                    null,
                    "μˆ˜λŸ‰μ€ μ΅œλŒ€ 9,999 κΉŒμ§€ ν—ˆμš©ν•©λ‹ˆλ‹€."));
        }

        // νŠΉμ • ν•„λ“œ μ˜ˆμ™Έκ°€ μ•„λ‹Œ 전체 μ˜ˆμ™Έ
        // 4. μ΅œμ’… κ°’(가격(itemPrice) * 개수(quantity))이 10,000원 미만인 경우
        if(item.getPrice() != null && item.getQuantity() != null){
            int resultPrice = item.getPrice() * item.getQuantity();

            if(resultPrice < 10_000) {
                bindingResult.addError(new ObjectError("item",
                        null,
                        null,
                        "가격 * μˆ˜λŸ‰μ˜ 합은 10,000원 이상이어야 ν•©λ‹ˆλ‹€.. ν˜„μž¬ κ°’ = " + resultPrice));
            }
        }

        if(bindingResult.hasErrors()){
            return "item/addForm"; // λ‹€μ‹œ μƒν’ˆ λ“±λ‘νŽ˜μ΄μ§€λ‘œ 이동
        }

        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/items/{itemId}";
    }
}

 

ν•΄λ‹Ή 글은 κΉ€μ˜ν•œ λ‹˜μ˜ 'μŠ€ν”„λ§ MVC 2탄 - λ°±μ—”λ“œ μ›Ή 개발 ν™œμš© 기술'을 μ°Έκ³ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

'πŸ“‚ μŠ€ν”„λ§ > κΈ°λ³Έ κ°œλ…' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

[Spring] 검증(Validation) - Validator  (0) 2023.05.02
[Spring] 검증(Validation) - bindingResult.rejectValue(), binding.reject()  (0) 2023.05.02
μŠ€ν”„λ§ 인터셉터(Spring Interceptor)  (0) 2023.04.30
μ„œλΈ”λ¦Ώ ν•„ν„°(Servlet Filter)  (0) 2023.04.29
λ©”μ‹œμ§€ & κ΅­μ œν™” (Message & Internationalization)  (0) 2023.04.25
    'πŸ“‚ μŠ€ν”„λ§/κΈ°λ³Έ κ°œλ…' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€
    • [Spring] 검증(Validation) - Validator
    • [Spring] 검증(Validation) - bindingResult.rejectValue(), binding.reject()
    • μŠ€ν”„λ§ 인터셉터(Spring Interceptor)
    • μ„œλΈ”λ¦Ώ ν•„ν„°(Servlet Filter)
    Amenable
    Amenable
    CS, μžλ°”, 자료ꡬ쑰, μ•Œκ³ λ¦¬μ¦˜, μŠ€ν”„λ§, μŠ€ν”„λ§ λΆ€νŠΈμ— ν•΄λ‹Ήν•˜λŠ” κ°œλ°œμ— κ΄€ν•œ λ‚΄μš©μ„ κ³΅μœ ν•©λ‹ˆλ‹€.

    ν‹°μŠ€ν† λ¦¬νˆ΄λ°”