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}";
}