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

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

Spring Boot - RoadMap 1

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

๐Ÿท Bean Validation - groups

์ง€๋‚œ ์‹œ๊ฐ„์— ๋ฐœ์ƒํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด,
๋™์ผํ•œ ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ๋“ฑ๋กํ•  ๋•Œ์™€ ์ˆ˜์ •ํ•  ๋•Œ ๊ฐ๊ฐ ๋‹ค๋ฅด๊ฒŒ ๊ฒ€์ฆํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž๐Ÿ˜€

1๏ธโƒฃ BeanValidation์˜ groups ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•œ๋‹ค.
2๏ธโƒฃ Item์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ItemSaveForm, ItemUpdateForm ๊ฐ™์€ ํผ ์ „์†ก์„ ์œ„ํ•œ ๋ณ„๋„์˜ ๋ชจ๋ธ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•œ๋‹ค.

โžก๏ธ ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Bean Validation์€ groups๋ผ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด์„œ ๋“ฑ๋ก์‹œ์— ๊ฒ€์ฆํ•  ๊ธฐ๋Šฅ๊ณผ ์ˆ˜์ •์‹œ์— ๊ฒ€์ฆํ•  ๊ธฐ๋Šฅ์„ ๊ฐ๊ฐ ๊ทธ๋ฃน์œผ๋กœ ๋‚˜๋ˆ„์–ด ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.


โœ”๏ธ groups ์ ์šฉ - ์ €์žฅ์šฉ groups ์ƒ์„ฑ

package hello.itemservice.domain.item;

public interface SaveCheck {
}

โœ”๏ธ groups ์ ์šฉ - ์ˆ˜์ •์šฉ groups ์ƒ์„ฑ

package hello.itemservice.domain.item;

public interface UpdateCheck {
}

โœ”๏ธ groups ์ ์šฉ - Item โžก๏ธ groups ์ ์šฉ

package hello.itemservice.domain.item;

import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.ScriptAssert;

import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class Item {

    @NotNull(groups = UpdateCheck.class) 
    private Long id;

    @NotBlank(groups = {SaveCheck.class, UpdateCheck.class})
    private String itemName;

    @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
    @Range(min = 1000, max = 1000000, groups = {SaveCheck.class, UpdateCheck.class})
    private Integer price;

    @NotNull(groups = {SaveCheck.class, UpdateCheck.class})
    // ๋“ฑ๋ก์‹œ์—๋งŒ ์ฒดํฌํ•˜๊ณ  ์ˆ˜์ •์‹œ์—๋Š” ์ฒดํฌ X
    @Max(value = 9999, groups = {SaveCheck.class})
    private Integer quantity;

    public Item() {
    }

    public Item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
}

โœ”๏ธ ValidationItemControllerV3 - ์ €์žฅ ๋กœ์ง์— SaveCheck Groups ์ ์šฉ

@PostMapping("/add")
    public String addItem2(@Validated(SaveCheck.class) @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {

        // ํŠน์ • ํ•„๋“œ๊ฐ€ ์•„๋‹Œ ๋ณตํ•ฉ ๋ฃฐ ๊ฒ€์ฆ
        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}";
    }

โœ”๏ธ ValidationItemControllerV3 - ์ˆ˜์ • ๋กœ์ง์— UpdateCheck Groups ์ ์šฉ

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

โœ”๏ธ ์‹คํ–‰ ๊ฒฐ๊ณผ

โฌ†๏ธ ๋“ฑ๋ก ์‹œ์—๋Š” 99999์— ๊ฑธ๋ฆฌ์ง€๋งŒ, ์ˆ˜์ •ํ•  ๋•Œ๋Š” ๊ฑธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค๐Ÿ‘๐Ÿป

๐Ÿ“Œ ์ฐธ๊ณ 
@Valid ์—๋Š” groups๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด ์—†๋‹ค.
๋”ฐ๋ผ์„œ groups๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด @Validated ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค!



## ๐Ÿท Form ์ „์†ก ๊ฐ์ฒด ๋ถ„๋ฆฌ - ํ”„๋กœ์ ํŠธ ์ค€๋น„ V4

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

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

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

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

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

3๏ธโƒฃ ์‹คํ–‰

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


๐Ÿท Form ์ „์†ก ๊ฐ์ฒด ๋ถ„๋ฆฌ - ์†Œ๊ฐœ

์‹ค๋ฌด์—์„œ๋Š” groups ๋ฅผ ์ž˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋ฐ, ๋“ฑ๋ก์‹œ ํผ์—์„œ ์ „๋‹ฌํ•˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ Item ๋„๋ฉ”์ธ ๊ฐ์ฒด์™€ ๋”ฑ ๋งž์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹คโ˜น๏ธ

๊ทธ๋ž˜์„œ ๋ณดํ†ต Item ์„ ์ง์ ‘ ์ „๋‹ฌ๋ฐ›๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๋ณต์žกํ•œ ํผ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ๊นŒ์ง€ ์ „๋‹ฌํ•  ๋ณ„๋„์˜ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ „๋‹ฌํ•œ๋‹คโ—๏ธ

  • ์˜ˆ๋ฅผ ๋“ค๋ฉด ItemSaveForm ์ด๋ผ๋Š” ํผ์„ ์ „๋‹ฌ๋ฐ›๋Š” ์ „์šฉ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ @ModelAttribute ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
  • ์ด๊ฒƒ์„ ํ†ตํ•ด ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ํผ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌ ๋ฐ›๊ณ , ์ดํ›„ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด์„œ Item์„ ์ƒ์„ฑํ•œ๋‹ค.

Item ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ฅผ ํผ ์ „๋‹ฌ ๋ฐ์ดํ„ฐ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ๊ทธ๋Œ€๋กœ ์ญ‰ ๋„˜๊ธฐ๋ฉด ํŽธ๋ฆฌํ•˜๊ฒ ์ง€๋งŒ,
์•ž์—์„œ ์„ค๋ช…ํ•œ ๊ฒƒ๊ณผ ๊ฐ™์ด ์‹ค๋ฌด์—์„œ๋Š” Item ์˜ ๋ฐ์ดํ„ฐ๋งŒ ๋„˜์–ด์˜ค๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋ฌด์ˆ˜ํ•œ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋„˜์–ด์˜จ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๋” ๋‚˜์•„๊ฐ€์„œ Item ์„ ์ƒ์„ฑํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ ๋‹ค๋ฅธ ๊ณณ์—์„œ ์ฐพ์•„์™€์•ผ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค๐Ÿค”

โžก๏ธ ๋”ฐ๋ผ์„œ ์ด๋ ‡๊ฒŒ ํผ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์„ ์œ„ํ•œ ๋ณ„๋„์˜ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ๋“ฑ๋ก, ์ˆ˜์ •์šฉ ํผ ๊ฐ์ฒด๋ฅผ ๋‚˜๋ˆ„๋ฉด ๋“ฑ๋ก, ์ˆ˜์ •์ด ์™„์ „ํžˆ ๋ถ„๋ฆฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— groups ๋ฅผ ์ ์šฉํ•  ์ผ์€ ๋“œ๋ฌผ๋‹คโ—๏ธ



๐Ÿท Form ์ „์†ก ๊ฐ์ฒด ๋ถ„๋ฆฌ - ๊ฐœ๋ฐœ

โœ”๏ธ ITEM ์›๋ณต

@Data
public class Item {

    private Long id;

    private String itemName;

    private Integer price;

    private Integer quantity;

    public Item() {
    }

    public Item(String itemName, Integer price, Integer quantity) {
        this.itemName = itemName;
        this.price = price;
        this.quantity = quantity;
    }
}
  • Item ์˜ ๊ฒ€์ฆ์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๊ฒ€์ฆ ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐ

โœ”๏ธ ItemSaveForm - ITEM ์ €์žฅ์šฉ ํผ

package hello.itemservice.web.validation.form;

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 ItemSaveForm {

    @NotBlank
    private String itemName;

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

    @NotNull
    @Max(value = 9999)
    private Integer quantity;
}
  • ID ๋Š” ํ•„์š”์—†์Œ

โœ”๏ธ ItemUpdateForm - ITEM ์ˆ˜์ •์šฉ ํผ

package hello.itemservice.web.validation.form;

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 ItemUpdateForm {

    @NotNull
    private Long id;

    @NotBlank
    private String itemName;

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

    //์ˆ˜์ •์—์„œ๋Š” ์ˆ˜๋Ÿ‰์€ ์ž์œ ๋กญ๊ฒŒ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Œ
    private Integer quantity;
}

โœ”๏ธ ValidationItemControllerV4 ์ˆ˜์ •

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 hello.itemservice.web.validation.form.ItemSaveForm;
import hello.itemservice.web.validation.form.ItemUpdateForm;
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 java.util.List;

@Slf4j
@Controller
@RequestMapping("/validation/v4/items")
@RequiredArgsConstructor
public class ValidationItemControllerV4 {

    private final ItemRepository itemRepository;

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

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

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

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

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

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

        // ์„ฑ๊ณต ๋กœ์ง
        Item item = new Item();
        item.setItemName(form.getItemName());
        item.setPrice(form.getPrice());
        item.setQuantity(form.getQuantity());

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

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

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

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

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

        Item itemParam = new Item();
        itemParam.setItemName(form.getItemName());
        itemParam.setPrice(form.getPrice());
        itemParam.setQuantity(form.getQuantity());

        itemRepository.update(itemId, itemParam);
        return "redirect:/validation/v4/items/{itemId}";
    }
}
  • ๊ธฐ์กด ์ฝ”๋“œ ์ œ๊ฑฐ : addItem(), addItemV2()
  • ๊ธฐ์กด ์ฝ”๋“œ ์ œ๊ฑฐ : edit(), editV2()
  • ์ถ”๊ฐ€ : addItem() , edit()

๐Ÿ“Œ ํผ ๊ฐ์ฒด ๋ฐ”์ธ๋”ฉ

@PostMapping("/add")
public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, 
	BindingResult bindingResult, RedirectAttributes redirectAttributes) {
	.
	.
}
  • Item ๋Œ€์‹ ์— ItemSaveform ์„ ์ „๋‹ฌ ๋ฐ›๊ณ  @Validated ๋กœ ๊ฒ€์ฆ๋„ ์ˆ˜ํ–‰ํ•˜๊ณ , BindingResult
    ๋กœ ๊ฒ€์ฆ ๊ฒฐ๊ณผ๋„ ๋ฐ›๋Š”๋‹ค.

๐Ÿ“Œ ํผ ๊ฐ์ฒด๋ฅผ Item์œผ๋กœ ๋ณ€ํ™˜

//์„ฑ๊ณต ๋กœ์ง
Item item = new Item(); 
item.setItemName(form.getItemName()); 
item.setPrice(form.getPrice()); 
item.setQuantity(form.getQuantity());
Item savedItem = itemRepository.save(item);
  • ํผ ๊ฐ์ฒด์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ Item ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  • ์ด๋ ‡๊ฒŒ ํผ ๊ฐ์ฒด ์ฒ˜๋Ÿผ ์ค‘๊ฐ„์— ๋‹ค๋ฅธ ๊ฐ์ฒด๊ฐ€ ์ถ”๊ฐ€๋˜๋ฉด ๋ณ€ํ™˜ํ•˜๋Š” ๊ณผ์ •์ด ์ถ”๊ฐ€๋œ๋‹ค.

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

โฌ†๏ธ ์ˆ˜์ • ์‹œ ์ˆ˜๋Ÿ‰์ด 0์ด์–ด๋„ ๋จ
โฌ†๏ธ ์ˆ˜์ • ์‹œ ์ˆ˜๋Ÿ‰์ด max์—ฌ๋„ ๋จ



๐Ÿท Bean Validation - HTTP ๋ฉ”์‹œ์ง€ ์ปจ๋ฒ„ํ„ฐ

@Valid, @Validated๋Š” HttpMessageConverter (@RequestBody)์—๋„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹คโ—๏ธ


โœ”๏ธ ValidationItemApiController ์ƒ์„ฑ

package hello.itemservice.web.validation;

import hello.itemservice.web.validation.form.ItemSaveForm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/validation/api/items")
public class ValidationItemApiController {

    @PostMapping("/add")
    public Object addItem(@RequestBody @Validated ItemSaveForm form, BindingResult bindingResult) {

        log.info("API ์ปจํŠธ๋กค๋Ÿฌ ํ˜ธ์ถœ");

        if (bindingResult.hasErrors()) {
            log.info("๊ฒ€์ฆ ์˜ค๋ฅ˜ ๋ฐœ์ƒ errors={}", bindingResult);
            return bindingResult.getAllErrors();
        }

        log.info("์„ฑ๊ณต ๋กœ์ง ์‹คํ–‰");
        return form;
    }
}

โœ”๏ธ Postman ํ…Œ์ŠคํŠธ

Postman์—์„œ Body โžก๏ธ raw โžก๏ธ JSON์„ ์„ ํƒ!

API์˜ ๊ฒฝ์šฐ 3๊ฐ€์ง€ ๊ฒฝ์šฐ๋ฅผ ๋‚˜๋ˆ„์–ด ์ƒ๊ฐํ•ด์•ผ ํ•œ๋‹คโ—๏ธ

  • 1๏ธโƒฃ ์„ฑ๊ณต ์š”์ฒญ : ์„ฑ๊ณต
  • 2๏ธโƒฃ ์‹คํŒจ ์š”์ฒญ : JSON์„ ๊ฐ์ฒด๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ ์ž์ฒด๊ฐ€ ์‹คํŒจํ•จ
  • 3๏ธโƒฃ ๊ฒ€์ฆ ์˜ค๋ฅ˜ ์š”์ฒญ : JSON์„ ๊ฐ์ฒด๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์€ ์„ฑ๊ณตํ–ˆ๊ณ , ๊ฒ€์ฆ์—์„œ ์‹คํŒจํ•จ

1๏ธโƒฃ-1) ์„ฑ๊ณต ์š”์ฒญ

POST http://localhost:8080/validation/api/items/add
{"itemName":"hello", "price":1000, "quantity": 10}

1๏ธโƒฃ-2) ์„ฑ๊ณต ์š”์ฒญ ๋กœ๊ทธ

API ์ปจํŠธ๋กค๋Ÿฌ ํ˜ธ์ถœ 
์„ฑ๊ณต ๋กœ์ง ์‹คํ–‰

2๏ธโƒฃ-1) ์‹คํŒจ ์š”์ฒญ

POST http://localhost:8080/validation/api/items/add
{"itemName":"hello", "price":"A", "quantity": 10}

โฌ†๏ธ price์˜ ๊ฐ’์— ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ๋ฌธ์ž๋ฅผ ์ „๋‹ฌํ•ด์„œ ์‹คํŒจํ•˜๊ฒŒ ๋งŒ๋“ค์–ด๋ณด์ž.


2๏ธโƒฃ-2) ์‹คํŒจ ์š”์ฒญ ๊ฒฐ๊ณผ

{
      "timestamp": "2021-04-20T00:00:00.000+00:00",
      "status": 400,
      "error": "Bad Request",
      "message": "",
      "path": "/validation/api/items/add"
}

2๏ธโƒฃ-3) ์‹คํŒจ ์š”์ฒญ ๋กœ๊ทธ

.w.s.m.s.DefaultHandlerExceptionResolver : Resolved
[org.springframework.http.converter.HttpMessageNotReadableException: JSON parse
error: Cannot deserialize value of type `java.lang.Integer` from String "A":
not a valid Integer value; nested exception is
com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize
value of type `java.lang.Integer` from String "A": not a valid Integer value
at [Source: (PushbackInputStream); line: 1, column: 30] (through reference
chain: hello.itemservice.domain.item.Item["price"])]

โฌ†๏ธ ์ด ๊ฒฝ์šฐ๋Š” Item ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ปจํŠธ๋กค๋Ÿฌ ์ž์ฒด๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š๊ณ  ๊ทธ ์ „์— ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
(+ ๋ฌผ๋ก  Validator๋„ ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค!)


3๏ธโƒฃ-1) ๊ฒ€์ฆ ์˜ค๋ฅ˜ ์š”์ฒญ

POST http://localhost:8080/validation/api/items/add
{"itemName":"hello", "price":1000, "quantity": 10000}

โฌ†๏ธ ์ˆ˜๋Ÿ‰(quantity)์ด 10000์ด๋ฉด BeanValidation @Max(9999) ์—์„œ ๊ฑธ๋ฆผ


3๏ธโƒฃ-2) ๊ฒ€์ฆ ์˜ค๋ฅ˜ ๊ฒฐ๊ณผ

{
  "codes": [
      "Max.itemSaveForm.quantity",
      "Max.quantity",
      "Max.java.lang.Integer",
      "Max"
  ],
  "arguments": [
  {
  "codes": [
              "itemSaveForm.quantity",
  "quantity"
          ],
          "arguments": null,
          "defaultMessage": "quantity",
          "code": "quantity"
  },
  9999
  ],
  "defaultMessage": "9999 ์ดํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค",
  "objectName": "itemSaveForm",
  "field": "quantity",
  "rejectedValue": 10000,
  "bindingFailure": false,
  "code": "Max"
} 

3๏ธโƒฃ-3) ๊ฒ€์ฆ ์š”์ฒญ ๋กœ๊ทธ

API ์ปจํŠธ๋กค๋Ÿฌ ํ˜ธ์ถœ
๊ฒ€์ฆ ์˜ค๋ฅ˜ ๋ฐœ์ƒ, errors=org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'itemSaveForm' on field 'quantity': rejected value [99999]; codes [Max.itemSaveForm.quantity,Max.quantity,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [itemSaveForm.quantity,quantity]; arguments []; default message [quantity],9999]; default message [9999 ์ดํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค]

โฌ†๏ธ ๋กœ๊ทธ๋ฅผ ๋ณด๋ฉด ๊ฒ€์ฆ ์˜ค๋ฅ˜๊ฐ€ ์ •์ƒ ์ˆ˜ํ–‰๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Œ


โœ”๏ธ @ModelAttribute vs @RequestBody

1๏ธโƒฃ @ModelAttribute

  • @ModelAttribute๋Š” ํ•„๋“œ ๋‹จ์œ„๋กœ ์ •๊ตํ•˜๊ฒŒ ๋ฐ”์ธ๋”ฉ์ด ์ ์šฉ๋œ๋‹ค.
  • ํŠน์ • ํ•„๋“œ๊ฐ€ ๋ฐ”์ธ๋”ฉ ๋˜์ง€ ์•Š์•„๋„ ๋‚˜๋จธ์ง€ ํ•„๋“œ๋Š” ์ •์ƒ ๋ฐ”์ธ๋”ฉ ๋˜๊ณ ,
  • Validator๋ฅผ ์‚ฌ์šฉํ•œ ๊ฒ€์ฆ๋„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

2๏ธโƒฃ @RequestBody

  • @RequestBody๋Š” HttpMessageConverter ๋‹จ๊ณ„์—์„œ JSON ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์ฒด๋กœ ๋ณ€๊ฒฝํ•˜์ง€ ๋ชปํ•˜๋ฉด ์ดํ›„ ๋‹จ๊ณ„ ์ž์ฒด๊ฐ€ ์ง„ํ–‰๋˜์ง€ ์•Š๊ณ  ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.
  • ์ปจํŠธ๋กค๋Ÿฌ๋„ ํ˜ธ์ถœ๋˜์ง€ ์•Š๊ณ , Validator๋„ ์ ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

AMOLAYO......

profile
Backend Developer

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