[Spring Boot][5] 5-1. ๊ฒ€์ฆ2 - Bean Validation

sorzzzzyยท2021๋…„ 9์›” 20์ผ
0

Spring Boot - RoadMap 1

๋ชฉ๋ก ๋ณด๊ธฐ
41/46
post-thumbnail

๐Ÿท Bean Validation - ์†Œ๊ฐœ

๊ฒ€์ฆ ๊ธฐ๋Šฅ์„ ์ง€๊ธˆ์ฒ˜๋Ÿผ ๋งค๋ฒˆ ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ์ƒ๋‹นํžˆ ๋ฒˆ๊ฑฐ๋กญ๋‹คโ˜น๏ธ
ํŠนํžˆ ํŠน์ • ํ•„๋“œ์— ๋Œ€ํ•œ ๊ฒ€์ฆ ๋กœ์ง์€
๋Œ€๋ถ€๋ถ„ ๋นˆ ๊ฐ’์ธ์ง€ ์•„๋‹Œ์ง€, ํŠน์ • ํฌ๊ธฐ๋ฅผ ๋„˜๋Š”์ง€ ์•„๋‹Œ์ง€์™€ ๊ฐ™์ด ๋งค์šฐ ์ผ๋ฐ˜์ ์ธ ๋กœ์ง์ด๋‹ค.

public class Item {
      private Long id;
      
      @NotBlank
      private String itemName;
      
      @NotNull
      @Range(min = 1000, max = 1000000)
      private Integer price;
      
      @NotNull
      @Max(9999)
      private Integer quantity;
      //...

โฌ†๏ธ ์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์ด, ๊ฒ€์ฆ ๋กœ์ง์„ ๋ชจ๋“  ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ณตํ†ตํ™”, ํ‘œ์ค€ํ™” ํ•œ ๊ฒƒ์„ ๋ฐ”๋กœ Bean Validation์ด๋ผ๊ณ  ํ•œ๋‹ค!

๐Ÿ’ก Bean Validation ์ด๋ž€โ“
๋จผ์ € Bean Validation์€ ํŠน์ •ํ•œ ๊ตฌํ˜„์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ Bean Validation 2.0(JSR-380)์ด๋ผ๋Š” ๊ธฐ์ˆ  ํ‘œ์ค€์ด๋‹ค.
์‰ฝ๊ฒŒ ์ด์•ผ๊ธฐํ•ด์„œ ๊ฒ€์ฆ ์• ๋…ธํ…Œ์ด์…˜๊ณผ ์—ฌ๋Ÿฌ ์ธํ„ฐํŽ˜์ด์Šค์˜ ๋ชจ์Œ์ด๋‹ค.
๋งˆ์น˜ JPA๊ฐ€ ํ‘œ์ค€ ๊ธฐ์ˆ ์ด๊ณ  ๊ทธ ๊ตฌํ˜„์ฒด๋กœ ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๊ฐ€ ์žˆ๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค!

๐Ÿ“Œ ํ•˜์ด๋ฒ„๋„ค์ดํŠธ Validator ๊ด€๋ จ ๋งํฌ



๐Ÿท Bean Validation - ์‹œ์ž‘

Bean Validation ๊ธฐ๋Šฅ์„ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์ฝ”๋“œ๋กœ ์•Œ์•„๋ณด์ž!
๋จผ์ € ์Šคํ”„๋ง๊ณผ ํ†ตํ•ฉํ•˜์ง€ ์•Š๊ณ , ์ˆœ์ˆ˜ํ•œ Bean Validation ์‚ฌ์šฉ๋ฒ• ๋ถ€ํ„ฐ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋กœ ์•Œ์•„๋ณด์ž๐Ÿ˜€


โœ”๏ธ Bean Validation ์˜์กด๊ด€๊ณ„ ์ถ”๊ฐ€

build.gradle
โžก๏ธ implementation 'org.springframework.boot:spring-boot-starter-validation'


โœ”๏ธ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ : Item - Bean Validation ์• ๋…ธํ…Œ์ด์…˜ ์ ์šฉ

package hello.itemservice.domain.item;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class Item {
    private Long id;
    
    @NotBlank
    private String itemName;
      
    @NotNull
    @Range(min = 1000, max = 1000000)
    private Integer price;
      
    @NotNull
    @Max(9999)
    private Integer quantity;
    
    public Item() {
	}
    
    public Item(String itemName, Integer price, Integer quantity) {
          this.itemName = itemName;
          this.price = price;
          this.quantity = quantity;
	} 
}

๊ฒ€์ฆ ์• ๋…ธํ…Œ์ด์…˜

  • @NotBlank : ๋นˆ๊ฐ’ + ๊ณต๋ฐฑ๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • @NotNull : null ์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • @Range(min = 1000, max = 1000000) : ๋ฒ”์œ„ ์•ˆ์˜ ๊ฐ’์ด์–ด์•ผ ํ•œ๋‹ค.
  • @Max(9999) : ์ตœ๋Œ€ 9999๊นŒ์ง€๋งŒ ํ—ˆ์šฉํ•œ๋‹ค

โœ”๏ธ BeanValidationTest - Bean Validation ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ

package hello.itemservice.validation;

import hello.itemservice.domain.item.Item;
import org.junit.jupiter.api.Test;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;

public class BeanValidationTest {

    @Test
    void beanValidation() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        Validator validator = factory.getValidator();

        Item item = new Item();
        item.setItemName(" "); //๊ณต๋ฐฑ
        item.setPrice(0);
        item.setQuantity(10000);

        Set<ConstraintViolation<Item>> violations = validator.validate(item);
        for (ConstraintViolation<Item> violation : violations) {
            System.out.println("violation = " + violation);
            System.out.println("violation = " + violation.getMessage());
        }

    }
}

โœ”๏ธ ๊ฒ€์ฆ๊ธฐ ์ƒ์„ฑ ๋ฐ ๊ฒ€์ฆ ์‹คํ–‰

1๏ธโƒฃ ๊ฒ€์ฆ๊ธฐ ์ƒ์„ฑ

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();

โฌ†๏ธ ์œ„์™€ ๊ฐ™์ด ๊ฒ€์ฆ๊ธฐ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค!
(์ดํ›„ ์Šคํ”„๋ง๊ณผ ํ†ตํ•ฉํ•˜๋Š” ๊ณผ์ •์—์„œ ์ด ์ฝ”๋“œ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—, ๊ทธ๋ƒฅ ์ด๋Ÿฐ๊ฒŒ ์žˆ๊ตฌ๋‚˜~ ์ •๋„๋กœ ์•Œ๊ณ  ๋„˜์–ด๊ฐ€์ž๐Ÿ˜Š)


2๏ธโƒฃ ๊ฒ€์ฆ ์‹คํ–‰

Set<ConstraintViolation<Item>> violations = validator.validate(item);

โฌ†๏ธ ๊ฒ€์ฆ ๋Œ€์ƒ(item)์„ ์ง์ ‘ ๊ฒ€์ฆ๊ธฐ์— ๋„ฃ๊ณ  ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›๋Š”๋‹ค. Set ์—๋Š” ConstraintViolation ์ด๋ผ๋Š” ๊ฒ€์ฆ ์˜ค๋ฅ˜๊ฐ€ ๋‹ด๊ธฐ๋Š”๋ฐ, ๊ฒฐ๊ณผ๊ฐ€ ๋น„์–ด์žˆ๋‹ค๋ฉด, ๊ฒ€์ฆ ์˜ค๋ฅ˜๊ฐ€ ์—†๋Š” ๊ฒƒ์ด๋‹ค!



๐Ÿท Bean Validation - ํ”„๋กœ์ ํŠธ ์ค€๋น„ V3

์•ž์„œ ๋งŒ๋“  ๊ธฐ๋Šฅ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, ์ปจํŠธ๋กค๋Ÿฌ์™€ ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์„ ๋ณต์‚ฌํ•˜์ž!


1๏ธโƒฃ ValidationItemControllerV3 ์ปจํŠธ๋กค๋Ÿฌ ์ƒ์„ฑ

  • hello.itemservice.web.validation.ValidationItemControllerV2 ๋ณต์‚ฌ
  • hello.itemservice.web.validation.ValidationItemControllerV3 ๋ถ™์—ฌ๋„ฃ๊ธฐ

2๏ธโƒฃ ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ ๋ณต์‚ฌ

  • validation/v2 ๋””๋ ‰ํ† ๋ฆฌ์˜ ๋ชจ๋“  ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ์„ validation/v3 ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๋ณต์‚ฌ
  • /resources/templates/validation/v3/ ํ•˜์œ„ 4๊ฐœ ํŒŒ์ผ ๋ชจ๋‘ URL ๊ฒฝ๋กœ ๋ณ€๊ฒฝ

3๏ธโƒฃ ์‹คํ–‰
-http://localhost:8080/validation/v3/items ์‹คํ–‰ ํ›„ ์›น ๋ธŒ๋ผ์šฐ์ €์˜ URL์ด validation/v3 ์œผ๋กœ ์ž˜ ์œ ์ง€๋˜๋Š”์ง€ ํ™•์ธ!



๐Ÿท Bean Validation - ์Šคํ”„๋ง ์ ์šฉ

โœ”๏ธ ValidationItemControllerV3 ์ฝ”๋“œ ์ˆ˜์ •

package hello.itemservice.web.validation;

import hello.itemservice.domain.item.Item;
import hello.itemservice.domain.item.ItemRepository;
import hello.itemservice.domain.item.SaveCheck;
import hello.itemservice.domain.item.UpdateCheck;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.validation.Valid;
import java.util.List;

@Slf4j
@Controller
@RequestMapping("/validation/v3/items")
@RequiredArgsConstructor
public class ValidationItemControllerV3 {

    private final ItemRepository itemRepository;

    @GetMapping
    public String items(Model model) {
        List<Item> items = itemRepository.findAll();
        model.addAttribute("items", items);
        return "validation/v3/items";
    }

    @GetMapping("/{itemId}")
    public String item(@PathVariable long itemId, Model model) {
        Item item = itemRepository.findById(itemId);
        model.addAttribute("item", item);
        return "validation/v3/item";
    }

    @GetMapping("/add")
    public String addForm(Model model) {
        model.addAttribute("item", new Item());
        return "validation/v3/addForm";
    }

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

        //ํŠน์ • ํ•„๋“œ๊ฐ€ ์•„๋‹Œ ๋ณตํ•ฉ ๋ฃฐ ๊ฒ€์ฆ
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }

        //๊ฒ€์ฆ์— ์‹คํŒจํ•˜๋ฉด ๋‹ค์‹œ ์ž…๋ ฅ ํผ์œผ๋กœ
        if (bindingResult.hasErrors()) {
            log.info("errors={} ", bindingResult);
            return "validation/v3/addForm";
        }

        //์„ฑ๊ณต ๋กœ์ง
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/validation/v3/items/{itemId}";
    }

    @GetMapping("/{itemId}/edit")
    public String editForm(@PathVariable Long itemId, Model model) {
        Item item = itemRepository.findById(itemId);
        model.addAttribute("item", item);
        return "validation/v3/editForm";
    }

//    @PostMapping("/{itemId}/edit")
    public String edit(@PathVariable Long itemId, @Validated @ModelAttribute Item item, BindingResult bindingResult) {

        //ํŠน์ • ํ•„๋“œ๊ฐ€ ์•„๋‹Œ ๋ณตํ•ฉ ๋ฃฐ ๊ฒ€์ฆ
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }

        if (bindingResult.hasErrors()) {
            log.info("errors={}", bindingResult);
            return "validation/v3/editForm";
        }

        itemRepository.update(itemId, item);
        return "redirect:/validation/v3/items/{itemId}";
    }

    @PostMapping("/{itemId}/edit")
    public String editV2(@PathVariable Long itemId, @Validated(UpdateCheck.class) @ModelAttribute Item item, BindingResult bindingResult) {

        //ํŠน์ • ํ•„๋“œ๊ฐ€ ์•„๋‹Œ ๋ณตํ•ฉ ๋ฃฐ ๊ฒ€์ฆ
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }

        if (bindingResult.hasErrors()) {
            log.info("errors={}", bindingResult);
            return "validation/v3/editForm";
        }

        itemRepository.update(itemId, item);
        return "redirect:/validation/v3/items/{itemId}";
    }

}

๊ฒ€์ฆ๊ธฐ ๋ถ€๋ถ„์„ ๋นผ๊ณ , addItemV1~V5 ๋ฅผ ๋ชจ๋‘ ์ง€์šด ๋’ค,
addItemV6 โžก๏ธ addItem ์œผ๋กœ ๋ฐ”๊พผ๋‹ค!!

๊ทธ๋Ÿฐ๋ฐ ์‹คํ–‰ํ•ด๋ณด๋ฉด ๊ฒ€์ฆ๊ธฐ ๋ถ€๋ถ„์ด ๋น ์กŒ์Œ์—๋„ Bean Validator ๊ฐ€ ์ž˜ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค๐Ÿค”

โžก๏ธ ์Šคํ”„๋ง ๋ถ€ํŠธ๊ฐ€ spring-boot-starter-validation ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋„ฃ์œผ๋ฉด ์ž๋™์œผ๋กœ Bean Validator๋ฅผ ์ธ์ง€ํ•˜๊ณ  ์Šคํ”„๋ง์— ํ†ตํ•ฉํ•œ๋‹คโ—๏ธ
โžก๏ธ LocalValidatorFactoryBean ์„ ๊ธ€๋กœ๋ฒŒ Validator๋กœ ๋“ฑ๋กํ•œ๋‹ค.
โžก๏ธ ์ด Validator๋Š” @NotNull ๊ฐ™์€ ์• ๋…ธํ…Œ์ด์…˜์„ ๋ณด๊ณ  ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
โžก๏ธ ์ด๋ ‡๊ฒŒ ๊ธ€๋กœ๋ฒŒ Validator๊ฐ€ ์ ์šฉ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— @Valid , @Validated ๋งŒ ์ ์šฉํ•˜๋ฉด ๋œ๋‹ค.


โœ”๏ธ ๊ฒ€์ฆ ์ˆœ์„œ

1๏ธโƒฃ @ModelAttribute ๊ฐ๊ฐ์˜ ํ•„๋“œ์— ํƒ€์ž… ๋ณ€ํ™˜ ์‹œ๋„

  • ์„ฑ๊ณตํ•˜๋ฉด ๋‹ค์Œ์œผ๋กœ
  • ์‹คํŒจํ•˜๋ฉด typeMismatch ๋กœ FieldError ์ถ”๊ฐ€

2๏ธโƒฃ Validator ์ ์šฉ

๐Ÿ’ก@ModelAttribute โžก๏ธ ๊ฐ๊ฐ์˜ ํ•„๋“œ ํƒ€์ž… ๋ณ€ํ™˜์‹œ๋„ โžก๏ธ ๋ณ€ํ™˜์— ์„ฑ๊ณตํ•œ ํ•„๋“œ๋งŒ BeanValidation ์ ์šฉ๐Ÿ’ก



๐Ÿท Bean Validation - ์—๋Ÿฌ ์ฝ”๋“œ

Bean Validation์ด ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ข€ ๋” ์ž์„ธํžˆ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ์œผ๋ฉด ์–ด๋–ป๊ฒŒ ํ• ๊นŒ๐Ÿค”โ“

  • Bean Validation ์„ ์ ์šฉํ•˜๊ณ  bindingResult ์— ๋“ฑ๋ก๋œ ๊ฒ€์ฆ ์˜ค๋ฅ˜ ์ฝ”๋“œ๋ฅผ ๋ณด์ž.
  • ์˜ค๋ฅ˜ ์ฝ”๋“œ๊ฐ€ ์• ๋…ธํ…Œ์ด์…˜ ์ด๋ฆ„์œผ๋กœ ๋“ฑ๋ก๋˜๋Š”๋ฐ ์ด๊ฒƒ์€ ๋งˆ์น˜ typeMismatch ์™€ ์œ ์‚ฌํ•˜๋‹ค!
  • NotBlank ๋ผ๋Š” ์˜ค๋ฅ˜ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ MessageCodesResolver ๋ฅผ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ๋ฉ”์‹œ์ง€ ์ฝ”๋“œ๊ฐ€ ์ˆœ์„œ๋Œ€๋กœ ์ƒ์„ฑ๋œ๋‹ค.

@NotBlank

  • NotBlank.item.itemName
  • NotBlank.itemName
  • NotBlank.java.lang.String
  • NotBlank

@Range

  • Range.item.price
  • Range.price
  • Range.java.lang.Integer
  • Range

โœ”๏ธ ๋ฉ”์‹œ์ง€ ๋“ฑ๋ก

errors.properties ์ˆ˜์ •

#Bean Validation ์ถ”๊ฐ€ 
NotBlank={0} ๊ณต๋ฐฑX 
Range={0}, {2} ~ {1} ํ—ˆ์šฉ 
Max={0}, ์ตœ๋Œ€ {1}

โฌ†๏ธ {0} ์€ ํ•„๋“œ๋ช…์ด๊ณ , {1}, {2} ...์€ ๊ฐ ์• ๋…ธํ…Œ์ด์…˜ ๋งˆ๋‹ค ๋‹ค๋ฆ„


โœ”๏ธ BeanValidation ๋ฉ”์‹œ์ง€ ์ฐพ๋Š” ์ˆœ์„œ

1๏ธโƒฃ ์ƒ์„ฑ๋œ ๋ฉ”์‹œ์ง€ ์ฝ”๋“œ ์ˆœ์„œ๋Œ€๋กœ messageSource ์—์„œ ๋ฉ”์‹œ์ง€ ์ฐพ๊ธฐ

2๏ธโƒฃ ์• ๋…ธํ…Œ์ด์…˜์˜ message ์†์„ฑ ์‚ฌ์šฉ โžก๏ธ @NotBlank(message = "๊ณต๋ฐฑ! {0}")

3๏ธโƒฃ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋ณธ ๊ฐ’ ์‚ฌ์šฉ โžก๏ธ ๊ณต๋ฐฑ์ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.



๐Ÿท Bean Validation - ์˜ค๋ธŒ์ ํŠธ ์˜ค๋ฅ˜

**Bean Validation์—์„œ ํŠน์ • ํ•„๋“œ(FieldError)๊ฐ€ ์•„๋‹Œ ํ•ด๋‹น ์˜ค๋ธŒ์ ํŠธ ๊ด€๋ จ ์˜ค๋ฅ˜(ObjectError)๋Š” ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์„๊นŒ๐Ÿค”โ“

@Data
@ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >= 10000")

public class Item {
	//...
}

โฌ†๏ธ @ScriptAssert() ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹คโ—๏ธ

โฌ†๏ธ ์‹คํ–‰ํ•ด๋ณด๋ฉด ์ž˜ ๋™์ž‘ํ•˜์ง€๋งŒ, ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•˜๊ธฐ์—๋Š” ์ œ์•ฝ์ด ๋งŽ๊ณ  ๋ณต์žกํ•˜๋‹คโ˜น๏ธ

๋”ฐ๋ผ์„œ ์˜ค๋ธŒ์ ํŠธ ์˜ค๋ฅ˜(๊ธ€๋กœ๋ฒŒ ์˜ค๋ฅ˜)์˜ ๊ฒฝ์šฐ @ScriptAssert ์„ ์–ต์ง€๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ ๋ณด๋‹ค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์˜ค๋ธŒ์ ํŠธ ์˜ค๋ฅ˜ ๊ด€๋ จ ๋ถ€๋ถ„๋งŒ ์ง์ ‘ ์ž๋ฐ” ์ฝ”๋“œ๋กœ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค!


โœ”๏ธ ValidationItemControllerV3 - ๊ธ€๋กœ๋ฒŒ ์˜ค๋ฅ˜ ์ถ”๊ฐ€

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

        //ํŠน์ • ํ•„๋“œ๊ฐ€ ์•„๋‹Œ ๋ณตํ•ฉ ๋ฃฐ ๊ฒ€์ฆ
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }

โฌ†๏ธ ์‹คํ–‰ ๊ตฌ์šฐ์šฐ์›ƒ



๐Ÿท Bean Validation - ์ˆ˜์ •์— ์ ์šฉ

์ˆ˜์ •์—๋„ ๊ฒ€์ฆ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž๐Ÿ˜€


โœ”๏ธ ValidationItemControllerV3 - edit() ๋ณ€๊ฒฝ

@PostMapping("/{itemId}/edit")
    public String edit(@PathVariable Long itemId, @Validated @ModelAttribute Item item, BindingResult bindingResult) {

        // ํŠน์ • ํ•„๋“œ๊ฐ€ ์•„๋‹Œ ๋ณตํ•ฉ ๋ฃฐ ๊ฒ€์ฆ
        if (item.getPrice() != null && item.getQuantity() != null) {
            int resultPrice = item.getPrice() * item.getQuantity();
            if (resultPrice < 10000) {
                bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }

        if (bindingResult.hasErrors()) {
            log.info("errors={}", bindingResult);
            return "validation/v3/editForm";
        }

        itemRepository.update(itemId, item);
        return "redirect:/validation/v3/items/{itemId}";
    }
  • edit() : Item ๋ชจ๋ธ ๊ฐ์ฒด์— @Validated ๋ฅผ ์ถ”๊ฐ€
  • ๊ฒ€์ฆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด editForm ์œผ๋กœ ์ด๋™ํ•˜๋Š” ์ฝ”๋“œ ์ถ”๊ฐ€

โœ”๏ธ validation/v3/editForm.html ๋ณ€๊ฒฝ

<style>
	.container {
		max-width: 560px;
    }
	.field-error {
  		border-color: #dc3545;
        color: #dc3545;
	}
    </style>

โฌ†๏ธ .field-error css ์ถ”๊ฐ€

<div th:if="${#fields.hasGlobalErrors()}">
	<p class="field-error" th:each="err :${#fields.globalErrors()}" th:text="${err}">๊ธ€๋กœ๋ฒŒ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€</p>
</div>

โฌ†๏ธ ๊ธ€๋กœ๋ฒŒ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€


โœ”๏ธ ์‹คํ–‰

โฌ†๏ธ ์‹คํ–‰ ๊ฒฐ๊ณผ ๐Ÿ‘๐Ÿป



๐Ÿท Bean Validation - ํ•œ๊ณ„

โœ”๏ธ ๋“ฑ๋ก์‹œ ๊ธฐ์กด ์š”๊ตฌ์‚ฌํ•ญ

  • ํƒ€์ž… ๊ฒ€์ฆ
    • ๊ฐ€๊ฒฉ, ์ˆ˜๋Ÿ‰์— ๋ฌธ์ž๊ฐ€ ๋“ค์–ด๊ฐ€๋ฉด ๊ฒ€์ฆ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ
  • ํ•„๋“œ ๊ฒ€์ฆ
    • ์ƒํ’ˆ๋ช… : ํ•„์ˆ˜, ๊ณต๋ฐฑX
    • ๊ฐ€๊ฒฉ : 1000์› ์ด์ƒ, ๋ฐฑ๋งŒ์› ์ดํ•˜
    • ์ˆ˜๋Ÿ‰ : ์ตœ๋Œ€ 9999
  • ํŠน์ • ํ•„๋“œ์˜ ๋ฒ”์œ„๋ฅผ ๋„˜์–ด์„œ๋Š” ๊ฒ€์ฆ
    • ๊ฐ€๊ฒฉ * ์ˆ˜๋Ÿ‰์˜ ํ•ฉ์€ 10,000์› ์ด์ƒ

โœ”๏ธ ์ˆ˜์ •์‹œ ์š”๊ตฌ์‚ฌํ•ญ

  • ๋“ฑ๋ก ์‹œ์—๋Š” quantity ์ˆ˜๋Ÿ‰์„ ์ตœ๋Œ€ 9999๊นŒ์ง€ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ˆ˜์ •์‹œ์—๋Š” ์ˆ˜๋Ÿ‰์„ ๋ฌด์ œํ•œ์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋“ฑ๋ก์‹œ์—๋Š” id ๊ฐ’์ด ์—†์–ด๋„ ๋˜์ง€๋งŒ, ์ˆ˜์ •์‹œ์—๋Š” id ๊ฐ’์ด ํ•„์ˆ˜์ด๋‹ค.

โœ”๏ธ ์ˆ˜์ • ์š”๊ตฌ์‚ฌํ•ญ ์ ์šฉ - Item ๋ณ€๊ฒฝ

@Data
public class Item {

    @NotNull    // ์ˆ˜์ • ์š”๊ตฌ์‚ฌํ•ญ ์ถ”๊ฐ€
    private Long id;

    @NotBlank
    private String itemName;

    @NotNull
    @Range(min = 1000, max = 1000000)
    private Integer price;

    @NotNull
//    @Max(9999)    // ์ˆ˜์ • ์š”๊ตฌ์‚ฌํ•ญ ์ถ”๊ฐ€
    private Integer quantity;

    public Item() {
    }

    public Item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
}
  • id : @NotNull ์ถ”๊ฐ€
  • quantity : @Max(9999) ์ œ๊ฑฐ

์‹คํ–‰ํ•ด๋ณด๋ฉด ์ˆ˜์ •์€ ์ •์ƒ ์ž‘๋™ํ•˜๋Š”๋ฐ, ๋“ฑ๋ก์—์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๐Ÿ˜‚
๋“ฑ๋ก์‹œ ํ™”๋ฉด์ด ๋„˜์–ด๊ฐ€์ง€ ์•Š์œผ๋ฉด์„œ, 'id': rejected value [null]; ์ด๋Ÿฐ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์˜ค๋Š”๋ฐ,

โžก๏ธ ๋“ฑ๋ก์‹œ์—๋Š” id ์— ๊ฐ’์ด ์—†๋Š”๋ฐ @NotNull id ๋ฅผ ์ ์šฉํ•œ ๊ฒƒ ๋•Œ๋ฌธ์— ๊ฒ€์ฆ์— ์‹คํŒจํ•˜๊ณ  ๋‹ค์‹œ ํผ ํ™”๋ฉด์œผ๋กœ ๋„˜์–ด์˜จ๋‹ค,, ๊ฒฐ๊ตญ ๋“ฑ๋ก ์ž์ฒด๋„ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ , ์ˆ˜๋Ÿ‰ ์ œํ•œ๋„ ๊ฑธ์ง€ ๋ชปํ•œ๋‹ค๐Ÿฅบ

์ด ๋ฌธ์ œ๋ฅผ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•  ์ง€๋Š” ๋‹ค์Œ ์‹œ๊ฐ„์— ๋ฐฐ์›Œ๋ณผ ์˜ˆ์ •์ด๋‹ค๐Ÿ˜Š


profile
Backend Developer

0๊ฐœ์˜ ๋Œ“๊ธ€