V4

์ €์žฅ์šฉ๊ณผ ์ˆ˜์ •์šฉ DTO๋ฅผ ๊ฐ๊ฐ ๋งŒ๋“ ๋‹ค.

Item

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

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

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

์ €์žฅ์šฉ ๊ฐ์ฒด

์ˆ˜์ •์šฉ ๊ฐ์ฒด

์ปจํŠธ๋กค๋Ÿฌ


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

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

์ปจํŠธ๋กค๋Ÿฌ

  • API์˜ ๊ฒฝ์šฐ 3๊ฐ€์ง€ ๊ฒฝ์šฐ๋ฅผ ์ƒ๊ฐํ•ด์•ผ ํ•œ๋‹ค.

    • ์„ฑ๊ณต ์š”์ฒญ (์„ฑ๊ณต)

    • ์‹คํŒจ ์š”์ฒญ : JSON์„ ๊ฐ์ฒด๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ ์‹คํŒจ (JSON parser error)

    • ๊ฒ€์ฆ ์˜ค๋ฅ˜ ์š”์ฒญ : JSON์„ ๊ฐ์ฒด๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์€ ์„ฑ๊ณตํ–ˆ์œผ๋‚˜ ๊ฒ€์ฆ์—์„œ ์‹คํŒจ

์‹คํŒจ ์š”์ฒญ : price์— ๋ฌธ์ž๋ฅผ ์ž…๋ ฅํ–ˆ์„ ๋•Œ

๊ฒ€์ฆ ์˜ค๋ฅ˜ ์š”์ฒญ : @Max์˜ ๋ฒ”์œ„๋ฅผ ์ดˆ๊ณผํ–ˆ์„ ๋•Œ

bindingResult.getAllErrors()๋Š” objectError์™€ FieldError๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์Šคํ”„๋ง์€ ์ด ๊ฐ์ฒด๋ฅผ JSON์œผ๋กœ ๋ณ€ํ™˜ํ•ด์„œ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌํ•œ๋‹ค. ์‹ค์ œ ๊ฐœ๋ฐœํ•  ๋•Œ๋Š” ์ด ๊ฐ์ฒด๋“ค์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜์ง€ ๋ง๊ณ  ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๋ฝ‘์•„์„œ ๋ณ„๋„์˜ API ์ŠคํŽ™์„ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค.

@ModelAttribute vs @RequestBody

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

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


์Šคํ”„๋ง๊ณผ Bean Validation

  • ์Šคํ”„๋ง์—์„œ๋Š” ์–ด๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜ ๊ฒ€์ฆ์„ ์œ„ํ•ด @Valid์™€ @Validated ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์‚ฌ์šฉ ๋ฐฉ์‹์— ์žˆ์–ด ์•ฝ๊ฐ„ ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค.

  • @Valid๋Š” jakarta.validation์— ํฌํ•จ๋˜์–ด ์žˆ๊ณ , @Validated๋Š” org.springframework.validation.annotation์— ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉฐ @Valid๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” spring-boot-starter-validation ์˜์กด์„ฑ์ด ํ•„์š”ํ•˜๋‹ค.

  • ๋‘ ์–ด๋…ธํ…Œ์ด์…˜ ๋ชจ๋‘ ๊ฐ์ฒด ํƒ€์ž…์—๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ ๊ฒ€์ฆํ•  ๊ฐ์ฒด ๋ฐ”๋กœ ์•ž์— ์œ„์น˜ํ•ด์•ผ ํ•˜๋ฉฐ ๊ฒ€์ฆ๋œ ๊ฒฐ๊ณผ๋Š” BindingResult์— ๋‹ด๊ธด๋‹ค.

  • ๊ฒ€์ฆ์€ ๋ฐ”์ธ๋”ฉ์˜ ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰ ์ฒ˜๋ฆฌ ๊ณผ์ •์ด๋ฉฐ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฐ”์ธ๋”ฉ์— ์„ฑ๊ณตํ•œ ํ•„๋“œ๋Š” ๊ฒ€์ฆ์ด ์ด๋ฃจ์–ด์ง„๋‹ค.

  • ๋งŒ์•ฝ ํ•„๋“œ์˜ ํƒ€์ž… ๋ณ€ํ™˜์ด ์‹คํŒจํ•˜๋ฉด ์‹คํŒจ ๊ฒฐ๊ณผ๊ฐ€ FieldError ๊ฐ์ฒด์— ๋‹ด๊ธฐ๊ณ  BindingResult์— ๋ณด๊ด€๋œ๋‹ค.

  • ํƒ€์ž… ๋ณ€ํ™˜์— ์‹คํŒจํ•œ ํ•„๋“œ๋Š” ๊ธฐ๋ณธ ๊ฐ’์ด ์ €์žฅ๋œ ์ƒํƒœ์—์„œ ๊ฒ€์ฆ์ด ์ด๋ฃจ์–ด์ง€์ง€๋งŒ Validator ๊ตฌํ˜„์ฒด์— ๋”ฐ๋ผ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ๊ณ  ๊ธฐ๋ณธ ๊ฒ€์ฆ์ด ์ด๋ฃจ์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

ํด๋ž˜์Šค ๊ตฌ์กฐ

img.png

๊ฒ€์ฆ ์ฒ˜๋ฆฌ ํ๋ฆ„๋„

img_1.png

์—ฌ๊ธฐ์„œ ConstraintValidator์—๋Š” ๋‹ค์–‘ํ•œ ๊ฒ€์ฆ๊ธฐ๋“ค์ด ์ •์˜๋˜์–ด ์žˆ๋‹ค.

img_2.png

์ปค์Šคํ…€ ๊ฒ€์ฆ ์• ๋…ธํ…Œ์ด์…˜ ๋งŒ๋“ค๊ธฐ

ConstraintValidator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์ปค์Šคํ…€ ๊ฒ€์ฆ ์• ๋…ธํ…Œ์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

img_3.png

์ฃผ์–ด์ง„ ์ œ์•ฝ ์กฐ๊ฑด A์— ๋Œ€ํ•ด ๊ฒ€์ฆ ๋Œ€์ƒ ํƒ€์ž… T๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค.

  • isValid() : ๊ฒ€์ฆ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ๊ฒ€์ฆ ๋Œ€์ƒ ๊ฐ์ฒด T๋Š” ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค.

  • initialize() : ๊ฒ€์ฆ๊ธฐ์˜ isValid() ํ˜ธ์ถœ์„ ์ค€๋น„ํ•˜๊ธฐ ์œ„ํ•ด ์ดˆ๊ธฐํ™”ํ•œ๋‹ค. ๊ฒ€์ฆ์— ์‚ฌ์šฉ๋˜๊ธฐ ์ „์— ๋จผ์ € ํ˜ธ์ถœ ๋œ๋‹ค.

Last updated