Back-End/Spring

[Spring] ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ(Validation) ๋ฐฉ๋ฒ•

Splin 2023. 2. 8.

Validation์ด๋ž€ ์–ด๋–ค ๋ฐ์ดํ„ฐ์˜ ๊ฐ’์ด ์œ ํšจํ•œ์ง€, ํƒ€๋‹นํ•œ์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ด๋ฉ”์ผ ์ฃผ์†Œ ์–‘์‹์€ admin@example.com์ธ๋ฐ, ํšŒ์› ๊ฐ€์ž…์„ ํ•  ๋•Œ ์ด๋ฉ”์ผ ์–‘์‹์ด ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด ์œ ํšจํ•˜์ง€ ์•Š์€ ์ด๋ฉ”์ผ์ด๋ฏ€๋กœ ํšŒ์› ๊ฐ€์ž…์„ ๋ง‰์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

UI์—์„œ javascript๋กœ "์ด๋ฉ”์ผ ์–‘์‹์ด ์ผ์น˜ํ•˜์ง€ ์•Š๋Š”๋‹ค"๋Š” ๊ฒƒ์€ UX ์ธก๋ฉด์—์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ํŽธ์˜๋ฅผ ์ฃผ๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ž˜๋ชป ์ž…๋ ฅํ•˜์—ฌ ์˜คํƒ€๊ฐ€ ๋ฐœ์ƒ ํ–ˆ์œผ๋‹ˆ ๋‹ค์‹œ ํ•œ ๋ฒˆ ํ™•์ธ์„ ํ•˜๋ผ๋Š” ์˜๋ฏธ๊ฐ€ ๊ฐ•ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, UI๋‹จ์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๋Š” ๊ฒƒ์€ ๋ณด์•ˆ์ ์ธ ์ธก๋ฉด์—์„œ ์•„๋ฌด ํšจ๊ณผ๊ฐ€ ์—†๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ณด์•ˆ์ ์ธ ์ธก๋ฉด์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ž€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์„œ๋ฒ„๋กœ ์ „์†ก๋˜๊ฑฐ๋‚˜, DB์— ์ €์žฅ๋˜์ง€ ์•Š๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

 

์˜์กด์„ฑ ์„ค์ •

<dependency>
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-validator</artifactId>
	    <version>6.1.0.Final</version>
</dependency>

์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋ถ€๋ถ„์„ ๋ช‡ ๊ฐœ ๊ฒ€์ƒ‰ํ•ด๋ณด๋ฉด validationa-api๋„ ์ถ”๊ฐ€ํ•ด์ฃผ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋Š”๋ฐ, hibernate-validator์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ฉด validationa-api์˜์กด์„ฑ๋„ ํ•จ๊ป˜ ์ถ”๊ฐ€๋˜๋ฏ€๋กœ validation-api ์˜์กด์„ ์ƒ๋žตํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค.

 

 

 

๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ๋ฒ•

๊ธฐ๋ณธ์ ์œผ๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” @Valid ๋˜๋Š” @Validated ์–ด๋…ธํ…Œ์ด์…˜์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ๋‘ ์–ด๋…ธํ…Œ์ด์…˜๋งŒ์œผ๋กœ๋Š” ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•  ์ˆ˜ ์—†๊ณ , ํด๋ž˜์Šค์— ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ œ์•ฝ์กฐ๊ฑด์— ๊ด€ํ•œ ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•ด์•ผ @Valid, @Validated๊ฐ€ ๋ถ™์€ ๊ฐ์ฒด๋ฅผ ์ œ์•ฝ์กฐ๊ฑด ์–ด๋…ธํ…Œ์ด์…˜๋“ค์„ ์ด์šฉํ•˜์—ฌ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•ด์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. @Valid, @Validated ์–ด๋…ธํ…Œ์ด์…˜์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๊ธฐ๋Šฅ์ด ๊ฐ™์ง€๋งŒ, @Validated๋Š” ํ•„๋“œ์—๋‹ค ์ œ์•ฝ์กฐ๊ฑด์— ๋Œ€ํ•œ ๊ทธ๋ฃน์„ ๋งŒ๋“ค์–ด ์ ์šฉ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜, BindingResult ๊ฐ์ฒด๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋„ฃ์–ด์ฃผ๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‹คํŒจํ•  ๋•Œ, ์—๋Ÿฌ ๋‚ด์šฉ์„ BindingResult๊ฐ์ฒด์— ๋ฐ”์ธ๋”ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, BindingResult๋ฅผ ์ด์šฉํ•ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‹คํŒจํ–ˆ์„ ๊ฒฝ์šฐ์— ๋”ฐ๋ฅธ ๋‹ค์–‘ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ค„ ์ˆ˜ ์žˆ๊ณ , DispatcherServlet์ด BindingResult๋ฅผ JSP์— ๋„ฃ์–ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— JSP์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‹คํŒจํ–ˆ์„ ๊ฒฝ์šฐ๋ฅผ ์ฒ˜๋ฆฌํ•ด์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งŒ์•ฝ @Valid, @Validated๋ฅผ ์‚ฌ์šฉ ์ค‘ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‹คํŒจํ•  ๋•Œ, ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ๊ฐ€ BindingResult ๊ฐ์ฒด๋ฅผ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค๋ฉด ๊ฒ€์ฆ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ MethodArgumentNotValidException์„ ๋‚ด๋ณด๋ƒ…๋‹ˆ๋‹ค.

์ฐธ๊ณ  ๋งํฌ

victolee93๋‹˜ ๋ธ”๋กœ๊ทธ(JSP์—์„œ BindingResult ์‚ฌ์šฉํ•˜๊ธฐ)

 

 

์ œ์•ฝ์กฐ๊ฑด ์–ด๋…ธํ…Œ์ด์…˜

๊ณต์‹๋ฌธ์„œ์—์„œ ์ž์ฃผ ์‚ฌ์šฉ๋  ๊ฒƒ ๊ฐ™์€ ์–ด๋…ธํ…Œ์ด์…˜๋งŒ ์ถ”๋ ค์„œ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค.

@AssertFalse ๊ฐ’์ด false ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@AssertTrue ๊ฐ’์ด true ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@Null ๊ฐ’์ด null์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@NotNull ๊ฐ’์ด null์ด ์•„๋‹Œ์ง€ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค.
@NotEmpty ๊ฐ’์ด null๋„ ๋นˆ๊ฒƒ๋„ ์•„๋‹Œ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. CharSequence, Collection, Map, ๋ฐฐ์—ด๋งŒ ์ง€์›๋ฉ๋‹ˆ๋‹ค.
@NotBlank ๊ฐ’์ด null์ด ์•„๋‹ˆ๊ณ , ํŠธ๋ฆผ ๋œ ๊ธธ์ด๊ฐ€ 0๋ณด๋‹ค ํฐ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@NotEmpty์™€์˜ ์ฐจ์ด์ ์€ ์ด ์ œ์•ฝ์€ CharSequence์—๋งŒ ์ ์šฉ๋˜๊ณ  ํŠธ๋ฆผ๋œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
@Email ์ง€์ •๋œ ๋ฌธ์ž ์‹œํ€€์Šค๊ฐ€ ์œ ํšจํ•œ ์ „์ž ๋ฉ”์ผ ์ฃผ์†Œ์ธ์ง€ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
์„ ํƒ์  ๋งค๊ฐœ ๋ณ€์ˆ˜ regexp ๋ฐ flags๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ „์ž ๋ฉ”์ผ๊ณผ ์ผ์น˜ํ•ด์•ผํ•˜๋Š” ์ถ”๊ฐ€ ์ •๊ทœ ํ‘œํ˜„์‹ (์ •๊ทœ์‹ ํ”Œ๋ž˜๊ทธ ํฌํ•จ)์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
@Max(value=) ๊ฐ’์ด ์ง€์ •๋œ ์ตœ๋Œ€๊ฐ’๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์€์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@Min(value=) ๊ฐ’์ด ์ง€์ •๋œ ์ตœ์†Œ๊ฐ’๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™์€์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@Size(min=, max=) ๊ฐ’์ด min=๊ณผ max= ์‚ฌ์ด์— ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.(๊ฒฝ๊ณ„ ํฌํ•จ)
@Positive ๊ฐ’์ด ์–‘์ˆ˜ ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@PositiveOrZero ๊ฐ’์ด ์–‘์ˆ˜์ด๊ฑฐ๋‚˜ 0์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@Negative ๊ฐ’์ด ์Œ์ˆ˜์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@NegativeOrZero ๊ฐ’์ด ์Œ์ˆ˜์ด๊ฑฐ๋‚˜ 0์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@Past ๊ฐ’์ด ๊ณผ๊ฑฐ์ผ์ž์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@PastOrPresent ๊ฐ’์ด ๊ณผ๊ฑฐ ๋˜๋Š” ํ˜„์žฌ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@Future ๋‚ ์งœ๊ฐ€ ๋ฏธ๋ž˜์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@FutureOrPresent ๋‚ ์งœ๊ฐ€ ํ˜„์žฌ ๋˜๋Š” ๋ฏธ๋ž˜์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@Pattern(regex=, flags=) ์ฃผ์–ด์ง„ ํ”Œ๋ž˜๊ทธ ๋งค์น˜๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ๊ฐ’์ด ์ •๊ทœ์‹ regex์™€ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.
@DecimalMax(value=, inclusive=) inclusive=false์ผ๋•Œ ์ง€์ •๋œ ์ตœ๋Œ€๊ฐ’ ๋ณด๋‹ค ์ž‘์€์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
inclusive=true ์ด๋ฉด ๊ฐ’์ด ์ง€์ •๋œ ์ตœ๋Œ€๊ฐ’๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์€์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ’์€ BigDecimal ๋ฌธ์ž์—ด ํ‘œํ˜„์— ๋”ฐ๋ผ ์ตœ๋Œ€๊ฐ’์˜ ๋ฌธ์ž์—ด ํ‘œํ˜„์ž…๋‹ˆ๋‹ค.
@DecimalMin(value=, inclusive=) ๊ฐ’์ด inclusive=false์ผ๋•Œ ์ง€์ •๋œ ์ตœ์†Œ๊ฐ’ ๋ณด๋‹ค ํฐ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
inclusive=true ์ด๋ฉด ๊ฐ’์ด ์ง€์ •๋œ ์ตœ์†Œ๊ฐ’๋ณด๋‹ค ํฌ๋‚˜ ๊ฐ™์€์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
๋งค๊ฐœ๋ณ€์ˆ˜ ๊ฐ’์€ BigDecimal ๋ฌธ์ž์—ด ํ‘œํ˜„์— ๋”ฐ๋ผ ์ตœ์†Œ๊ฐ’์˜ ๋ฌธ์ž์—ด ํ‘œํ˜„์ž…๋‹ˆ๋‹ค.
@Digits(integer=, fraction=) ๊ฐ’์ด integer=์™€ fraction=์— ์˜ํ•ด ์ง€์ •๋œ ์ž๋ฆฌ์ˆ˜์˜ ์ˆซ์ž์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@CreditCardNumber(ignoreNonDigitCharacters=) ์‹ ์šฉ์นด๋“œ ๋ฒˆํ˜ธ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. (์‹ ์šฉ ์นด๋“œ ์œ ํšจ์„ฑ์ด ์•„๋‹Œ, ์‚ฌ์šฉ์ž ์‹ค์ˆ˜๋ฅผ ํ™•์ธํ•˜๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•ฉ๋‹ˆ๋‹ค.)
ignoreNonDigitCharacters๋Š” ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ๋ฌธ์ž๋ฅผ ๋ฌด์‹œํ•˜๋„๋ก ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. (๊ธฐ๋ณธ false)
@Length(min=, max=) ๋ฌธ์ž์—ด์ด ์ตœ์†Œ๊ฐ’๊ณผ ์ตœ๋Œ€๊ฐ’ ์‚ฌ์ด์— ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@Range(min=, max=) ์ˆซ์ž๊ฐ€ ์ตœ์†Œ๊ฐ’๊ณผ ์ตœ๋Œ€๊ฐ’ ์‚ฌ์ด์— ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
@URL(protocol=, host=, port=, regexp=, flags=) protocol, host, port๊ฐ€ ์ง€์ •๋œ ๊ฒฝ์šฐ ํ•ด๋‹น URL ์กฐ๊ฐ์ด ์ง€์ •๋œ ๊ฐ’๊ณผ ์ผ์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
regexp, flags๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด URL์ด ์ผ์น˜ํ•ด์•ผ ํ•˜๋Š” ์ถ”๊ฐ€ ์ •๊ทœ์‹(์ •๊ทœ์‹ ํ”Œ๋ž˜๊ทธ ํฌํ•จ)์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ธฐ๋ณธ์ ์œผ๋กœ ์ด ์ œ์•ฝ ์กฐ๊ฑด์€ ์ฃผ์–ด์ง„ ๋ฌธ์ž์—ด์ด ์œ ํšจํ•œ URL์„ ๋‚˜ํƒ€๋‚ด๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด java.net.URL ์ƒ์„ฑ์ž๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

 

 

@Vaild

Post์š”์ฒญ ์‹œ @RequestBody๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์˜ˆ์‹œ๋กœ ๋“ค๊ฒ ์Šต๋‹ˆ๋‹ค.

 

UserDTO.java

public class UserDTO {
    @NotNull
    private Long id;
    @NotEmpty(message = "fail")
    private String password;
    private String name;
    
	// Getter, Setter ์ƒ๋žต
}

DTOํด๋ž˜์Šค์— ์ œ์•ฝ์กฐ๊ฑด ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์ค๋‹ˆ๋‹ค. message์†์„ฑ์„ ์ด์šฉํ•ด์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ์˜ message๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

ValidController.java

@RestController
@RequestMapping("/valid")
public class ValidController {

	@PostMapping
	public void valid(@RequestBody @Valid UserDTO userDTO, BindingResult result) {
		
		if(result.hasErrors()) {
			result.getAllErrors()
				.forEach(objectError -> {
					System.err.println("code : " + objectError.getCode());
					System.err.println("message : " + objectError.getDefaultMessage());
					System.err.println("name : " + objectError.getObjectName());
				});
		}
	}
}

์œ„์น˜์™€ ์ƒ๊ด€์—†์ด ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•  ๊ฐ์ฒด์— @Vaild๋ฅผ ๋ถ™์—ฌ์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. @RequestBody ์•ž์— ๋ถ™์—ฌ๋„ ๋ฉ๋‹ˆ๋‹ค.

BindingResult์—์„œ ๋ชจ๋“  ์—๋Ÿฌ์ •๋ณด๋ฅผ ์–ป์œผ๋ ค๋ฉด getAllErrors()๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. getAllErrors()๋Š” ์—๋Ÿฌ์ •๋ณด๋ฅผ List<ObjectError>์— ๋‹ด์•„ Return ํ•˜๋Š”๋ฐ, ์œ„์˜ ๊ฒฝ์šฐ์—์„  List๋ฅผ Stream์œผ๋กœ ์—๋Ÿฌ์ •๋ณด๋ฅผ ์ฝ˜์†”์— ์ถœ๋ ฅํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐํƒ€ ๋ฉ”์†Œ๋“œ๋“ค์€ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

 

์ฐธ๊ณ  ๋งํฌ
BindingResult ๊ณต์‹๋ฌธ์„œ

 

์ฒ˜๋ฆฌ์ˆœ์„œ

  1. @Valid๊ฐ€ ์„ ์–ธ๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— UserDTO ํด๋ž˜์Šค์˜ ์ œ์•ฝ์กฐ๊ฑด ์–ด๋…ธํ…Œ์ด์…˜(@Notnull, @NotEmpty)์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  2. ๋ชจ๋“  ํ•„๋“œ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜๋‹ค๋ฉด ๋ฌธ์ œ์—†์ด ์ž‘์„ฑ๋œ ๋กœ์ง์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  3. ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š๋Š” ํ•„๋“œ๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ์— ๋Œ€ํ•œ ์—๋Ÿฌ ์ •๋ณด๋ฅผ BindingResult๋ณ€์ˆ˜์— ๋‹ด์Šต๋‹ˆ๋‹ค.
    • BindingResult์— ๊ฐ’์ด ์žˆ๋‹ค๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‹คํŒจํ•œ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์‹คํŒจํ•œ ๊ฒฝ์šฐ์— ์‹คํ–‰ํ•  ๋กœ์ง์„ ๋งŒ๋“ค์–ด์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

DTO๊ฐ€ ๋‹ค๋ฅธ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ง€๋Š” ๊ฒฝ์šฐ

๋งŒ์•ฝ DTO์•ˆ์— ๋ฉค๋ฒ„ ํ•„๋“œ์˜ ํƒ€์ž…์ด primitive type ์ด๋‚˜ String ํด๋ž˜์Šค ์™ธ์— ๋˜ ๋‹ค๋ฅธ ํด๋ž˜์Šค๊ฐ€ ์žˆ์„ ๋•Œ ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ Validationํ•˜๋ ค๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ๋˜ ๋‹ค๋ฅธ ํด๋ž˜์Šค์— @Valid๋ฅผ ์ ์šฉํ•˜๊ณ  ํ•ด๋‹น ํด๋ž˜์Šค์—์„œ ๋‹ค์‹œ annotation์„ ์ ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

public class UserDTO {
    @NotNull
    private Long id;
    @NotEmpty(message = "fail")
    private String password;
    private String name;
    @Valid
    private Address address;
}

public class Address {
	@NotEmpty
	private String address;
	@NotNull
	private int addressNum;
}

 

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ

ํ…Œ์ŠคํŠธ๋Š” Validator๋ฅผ ์‚ฌ์šฉํ•œ ๊ฐ์ฒด์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๋Š” ํ…Œ์ŠคํŠธ์™€ MockMvc๋ฅผ ์‚ฌ์šฉํ•œ Controller๋กœ์ง์„ ๊ฒ€์ฆํ•˜๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๊ฐ์ฒด์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ (Validator ์‚ฌ์šฉ)

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {WebMvcContextConfiguration.class, ApplicationConfig.class})
public class ValidTest {
	
	private static Validator validator;
	private static ValidatorFactory factory;
	
	@BeforeClass
	public static void init() {
		factory = Validation.buildDefaultValidatorFactory();
		validator = factory.getValidator();
	}
		
	@Test
	public void validFailTest() throws Exception {
		UserDTO user = new UserDTO();
		user.setName("name");
		
		Set<ConstraintViolation<UserDTO>> set = validator.validate(user);
		
		for(ConstraintViolation<UserDTO> v : set)
			System.out.println(v.getMessage());
	}
}

๊ฐ์ฒด์™€ @Vaild๋ฅผ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ValidatorFactory์˜ getValidator()๋กœ Validator๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ ์‚ฌ์šฉํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ValidatorFactory, Validator๋Š” @BeforClass๋ฅผ ์ด์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์ดˆ๊ธฐํ™” ํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Validator์˜ validate()์— ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ•  ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์ฃผ์—ˆ์„ ๋•Œ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‹คํŒจํ•˜๊ฒŒ ๋˜๋ฉด, Set<ConstraintViolation<๊ฒ€์‚ฌํ•  ๊ฐ์ฒด>>์— ์—๋Ÿฌ์ •๋ณด๋ฅผ ๋‹ด์•„ Returnํ•˜๊ฒŒ ๋˜๊ณ , ConstraintViolation<๊ฒ€์‚ฌํ•  ๊ฐ์ฒด>๋ฅผ ์ด์šฉํ•ด ์—๋Ÿฌ์ •๋ณด๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ฐธ๊ณ  ๋งํฌ
ConstraintViolation<T> ๊ณต์‹๋ฌธ์„œ

 

 

Controller๋กœ์ง ๊ฒ€์ฆ (MockMvc ์‚ฌ์šฉ)

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {WebMvcContextConfiguration.class, ApplicationConfig.class})
public class ValidTest {
	@Autowired
	public ValidController validController;
	
	private MockMvc mockMvc;
	
	@Before
	public void createController() {
		MockitoAnnotations.initMocks(this);
		mockMvc = MockMvcBuilders.standaloneSetup(validController).build();
	}
	
	@Test
	public void validFailTest() throws Exception {
		UserDTO user = new UserDTO();
		user.setName("name");
				
		ObjectMapper mapper = new ObjectMapper();
		this.mockMvc.perform(post("/valid")
				.characterEncoding("UTF-8")
				.content(mapper.writeValueAsString(user))
				.contentType(MediaType.APPLICATION_JSON))
				.andExpect(status().isOk());
	}
}

Http์™€ ๊ฐ™์ด Controller๋กœ์ง์„ ๊ฒ€์ฆํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Controller๋ฅผ ์ฃผ์ž…๋ฐ›๊ณ  mockMvc๋ฅผ ํ•ด๋‹น Controller๋กœ build()ํ•ด์ค๋‹ˆ๋‹ค.

ValidController์—์„œ Post์š”์ฒญ ์‹œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— Json์„ ์ „์†กํ•ด ์ฃผ์–ด์•ผ ํ•˜๋Š”๋ฐ, ObjectMapper์˜ writeValueAsString()์— ๊ฐ์ฒด๋ฅผ ๋„ฃ์œผ๋ฉด ๊ฐ์ฒด์˜ ํ•„๋“œ์™€ ํ•„๋“œ์˜ ๊ฐ’์„ Json์œผ๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ฃผ์˜ํ•  ์ 

  • Json์„ ์ „์†กํ•  ๋•Œ body = <no character encoding set>์ฒ˜๋Ÿผ body๋ฅผ ์ธ์‹ํ•˜์ง€ ๋ชปํ•˜๊ฑฐ๋‚˜ ํ•œ๊ธ€์ด ๊นจ์ง€๋Š” ๊ฒฝ์šฐ, MockMvc๋ฅผ build()ํ•  ๋•Œ, Filter๋ฅผ ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • mockMvc = MockMvcBuilders.standaloneSetup(reservationInfoApiController).addFilters(new CharacterEncodingFilter("UTF-8",true)).build();
  • perform(post("/valid")์—์„œ post()๋Š” MockMvcRequestBuilders.post()์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด import static์„ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.
    • import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
  • javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'.์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์•„๋ž˜์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    <version>3.0.0</version>
</dependency>

์ฐธ๊ณ  ๋งํฌ
MockMvc๋ฅผ ์ด์šฉํ•œ WebAPI ํ…Œ์ŠคํŠธ

 

 

@Validated

@Validated ์–ด๋…ธํ…Œ์ด์…˜์€ ๊ธฐ๋ณธ์ ์œผ๋กœ @Valid์™€ ๊ธฐ๋Šฅ์ด ๊ฐ™์ง€๋งŒ, ํ•„๋“œ์—๋‹ค ์ œ์•ฝ์กฐ๊ฑด์— ๋Œ€ํ•œ ๊ทธ๋ฃน์„ ๋งŒ๋“ค์–ด ์ ์šฉ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Valid๋Š” ์ œ์•ฝ์กฐ๊ฑด์„ ๋‹ฌ์•„๋†“์€ ์†์„ฑ์— ๋Œ€ํ•ด ์ „๋ถ€ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ๋งŒ์•ฝ ์ œ์•ฝ์กฐ๊ฑด ์ค‘ ์›ํ•˜๋Š” ์†์„ฑ๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ณ ์‹ถ์€ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด @Validated ์ž…๋‹ˆ๋‹ค.

์›ํ•˜๋Š” ์†์„ฑ๋“ค๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๋ ค๋ฉด ๋จผ์ € ๊ทธ๋ฃนํ•‘์„ ํ•ด๋†“์•„์•ผ ํ•˜๋Š”๋ฐ, ํ•„๋“œ์— ๋Œ€ํ•œ ์ œ์•ฝ์กฐ๊ฑด ๊ทธ๋ฃนํ•‘์€ ๋ฉ”์†Œ๋“œ ์„ ์–ธ์ด ์—†๋Š” ๋งˆ์ปค ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

ValidationGroups.java

public class ValidationGroups {
	public interface group1 {}
	public interface group2 {}
}

ValidationGroupsํด๋ž˜์Šค์— ๋ฉ”์†Œ๋“œ ์„ ์–ธ์ด ์—†๋Š” ๋งˆ์ปค ์ธํ„ฐํŽ˜์ด์Šค group1, group2๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ํ•ด๋‹น ๊ทธ๋ฃน์— ์›ํ•˜๋Š” ํ•„๋“œ๋ฅผ ๊ทธ๋ฃนํ•‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

UserDTO.java

public class UserDTO {
    @NotNull(groups = {ValidationGroups.group1.class})
    private Long id;
    @NotEmpty(message = "fail", groups = {ValidationGroups.group2.class})
    private String password;
    private String name;
    
	// Getter,Setter ์ƒ๋žต
}

๊ธฐ์กด์˜ UserDTO์˜ ํ•„๋“œ๋ฅผ groups์†์„ฑ์„ ์ด์šฉํ•ด ๊ทธ๋ฃนํ•‘ํ•ฉ๋‹ˆ๋‹ค.

 

ValidController.java

@RestController
@RequestMapping("/valid")
public class ValidController {

	@PostMapping
	public void valid(@RequestBody @Validated(ValidationGroups.group1.class) UserDTO userDTO, BindingResult result) {
		
		if(result.hasErrors()) {
			result.getAllErrors()
				.forEach(objectError -> {
					System.err.println("code : " + objectError.getCode());
					System.err.println("message : " + objectError.getDefaultMessage());
					System.err.println("name : " + objectError.getObjectName());
				});
		}
	}
}

 

@Validated๋ฅผ ์„ ์–ธํ•˜๊ณ  ๊ด„ํ˜ธ๋ฅผ ์—ด์–ด ์›ํ•˜๋Š” ๊ทธ๋ฃน์„ ๋„ฃ์–ด์ฃผ๋ฉด ํ•ด๋‹น ๊ทธ๋ฃน์— ์†ํ•ด์žˆ๋Š” ํ•„๋“œ๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.(ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.) @Valid์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์œ„์น˜๋Š” ์ƒ๊ด€์—†์Šต๋‹ˆ๋‹ค.

 

์ฃผ์˜ํ•  ์ 

  • @Validated์— ๊ทธ๋ฃน์„ ๋„ฃ์–ด์ฃผ์ง€ ์•Š์œผ๋ฉด Controller์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

 

@PathVariable, @RequestParam์—์„œ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

@PathVariable, @RequestParam์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

MethodValidationPostProcessor Bean ๋“ฑ๋ก

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
         return new MethodValidationPostProcessor();
    }

๋จผ์ € @PathVariable, @RequestParam์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๋ ค๋ฉด MethodValidationPostProcessor๋ฅผ Bean์œผ๋กœ ๋“ฑ๋กํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

 

ValidController.java

@RestController
@RequestMapping("/valid")
@Validated
public class ValidController {
	
	@GetMapping("/{id}")
	public String parameterValid(@PathVariable @Email String id) {
		return id;
	}
}

Controllerํด๋ž˜์Šค์— @Validated๋ฅผ ๋ถ™์—ฌ์ฃผ๊ณ , ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ•  @PathVariable, @RequestParam์— ์ œ์•ฝ์กฐ๊ฑด ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

 

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ

	@Test
	public void parameterValidTest() throws Exception {
		
		this.mockMvc.perform(get("/valid/hello")
				.characterEncoding("UTF-8")
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print());
	}

MockMvc๋ฐฉ์‹์œผ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ…Œ์ŠคํŠธํ•ด๋ณด๋ฉด, ConstraintViolationException ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฃผ์˜ํ•  ์ 

  • Controller ํด๋ž˜์Šค์— @Validated๋ฅผ ๋ถ™์—ฌ์ฃผ๊ฒŒ ๋˜๋ฉด, BindingResult์— ์—๋Ÿฌ์ •๋ณด๋ฅผ ๋„ฃ์–ด ์‚ฌ์šฉํ•˜๋˜ ๋ฉ”์„œ๋“œ์—์„œ๋„ Exception์„ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ์˜ํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ๊ตฌ๊ธ€๋งํ•ด๋ณด๋‹ˆ @PathVariable๊ณผ BindingResult๋Š” ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.
    • ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” @ExceptionHandler๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ๋ฅผ ์ œ์–ดํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

 

ConstraintViolationException๊ณผ MethodArgumentNotValidException์˜ ์ฐจ์ด์ 

@Vaild๋ฅผ ์ด์šฉํ•˜๋‹ค ๋ฐœ์ƒํ•œ Exception์€ MethodArgumentNotValidException์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๊ฐ™์€ ์ œ์•ฝ์กฐ๊ฑด์„ ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ ์™œ @PathVariable์—์„œ ์ œ์•ฝ์กฐ๊ฑด ์–ด๋…ธํ…Œ์ด์…˜(@Email)์„ ์ด์šฉํ•˜๋ฉด ์™œ ConstraintViolationException์ด ๋ฐœ์ƒํ• ๊นŒ์š”? ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉด ๊ฐ Exception์— ๋Œ€ํ•œ ์„ค๋ช…์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ConstraintViolationException : ์ œ์•ฝ์กฐ๊ฑด ์œ„๋ฐ˜์˜ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ ํ•ฉ๋‹ˆ๋‹ค.
  • MethodArgumentNotValidException : @Valid ์ฃผ์„์ด ๋‹ฌ๋ฆฐ ์ธ์ˆ˜์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ฐ€ ์‹คํŒจํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. 5.3๋ถ€ํ„ฐ BindException์„ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค.

์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์‹œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ƒํ™ฉ์—์„œ @Valid๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์œผ๋ฉด ConstraintViolationException, ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด MethodArgumentNotValidException๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค.

ํ™•์ธ ๊ฒฐ๊ณผ, @Validated๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ MethodArgumentNotValidException์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

 

 

Custom Constraint Annotation

์ œ์•ฝ์กฐ๊ฑด ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ๋ชจ๋“  ์ œ์•ฝ์กฐ๊ฑด์„ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•œ ์ œ์•ฝ์กฐ๊ฑด์„ ์ง์ ‘ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ๋ฅผ ์œ„ํ•ด UserDTO์˜ nameํ•„๋“œ์— Splin์„ ์ œ์™ธํ•œ ๋‹ค๋ฅธ ์ด๋ฆ„์€ ์˜ฌ ์ˆ˜ ์—†๋Š” ์ œ์•ฝ์กฐ๊ฑด ์–ด๋…ธํ…Œ์ด์…˜์„ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

NameConstraint.java(Custom Constraint annotation)

@Documented
@Constraint(validatedBy = NameConstraintValidator.class)
@Retention(RUNTIME)
@Target({ TYPE, FIELD, PARAMETER })
public @interface NameConstraint {
	String message() default "Not Exist Name";
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
}

@interface๋ฅผ ์‚ฌ์šฉํ•ด Custom annotation์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, @NameConstraint๋ผ๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@interface์œ„์— ๋ถ™์€ ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์–ด๋…ธํ…Œ์ด์…˜์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

 

Metadata Annotation

๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์œ„ํ•œ ๋ฐ์ดํ„ฐ์ž…๋‹ˆ๋‹ค. ์ฆ‰, ์–ด๋–ค ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์กฐํ™”๋œ ์ •๋ณด๋ฅผ ๋ถ„์„, ๋ถ„๋ฅ˜ํ•˜๊ณ  ๋ถ€๊ฐ€์  ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด ๊ทธ ๋ฐ์ดํ„ฐ ๋’ค์— ํ•จ๊ป˜ ๋”ฐ๋ผ๊ฐ€๋Š” ์ •๋ณด๋ฅผ ๋งํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์—์„œ ์‚ฌ์šฉ๋œ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…ํ•˜์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • @Documented : ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ํด๋ž˜์Šค๊ฐ€ javadoc์™€ ๊ฐ™์ด ๋ฌธ์„œํ™” ๋  ๋•Œ, ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์ด ์ ์šฉ๋˜์—ˆ์Œ์„ ๋ช…์‹œํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  • @Constraint(validatedBy = Class<T>(์—ฌ๊ธฐ์„œ๋Š” NameConstraintValidator.class)) : ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‚ฌ์šฉ๋  Validator๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • @Retention(RUNTIME) : ์–ด๋…ธํ…Œ์ด์…˜์˜ ๋ผ์ดํ”„ ์‚ฌ์ดํด์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    • RetentionPolicy.SOURCE : ์†Œ์Šค ์ฝ”๋“œ(.java)๊นŒ์ง€ ๋‚จ์•„์žˆ์Šต๋‹ˆ๋‹ค.
    • RetentionPolicy.CLASS : ํด๋ž˜์Šค ํŒŒ์ผ(.class)๊นŒ์ง€ ๋‚จ์•„์žˆ์Šต๋‹ˆ๋‹ค.(=๋ฐ”์ดํŠธ ์ฝ”๋“œ)
      • class ํŒŒ์ผ๋งŒ ์กด์žฌํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋„ ํƒ€์ž…์ฒด์ปค, IDE ๋ถ€๊ฐ€๊ธฐ๋Šฅ ๋“ฑ์„ ์‚ฌ์šฉํ• ์ˆ˜ ์žˆ์œผ๋ ค๋ฉด CLASS ์ •์ฑ…์ด ํ•„์š”
    • RetentionPolicy.RUNTIME : ๋Ÿฐํƒ€์ž„๊นŒ์ง€ ๋‚จ์•„์žˆ์Šต๋‹ˆ๋‹ค.(=์‚ฌ์‹ค์ƒ ์•ˆ ์‚ฌ๋ผ์ง)
      • ์—ฌ๊ธฐ์—์„œ๋Š” import static์œผ๋กœ RetentionPolicy๋ฅผ ์„ ์–ธํ–ˆ์Šต๋‹ˆ๋‹ค.
  • @Target({ TYPE, FIELD, PARAMETER }) : ์–ด๋…ธํ…Œ์ด์…˜์ด ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ๋Š” ์œ„์น˜๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. (enum์œผ๋กœ ๋œ ElementType์˜ ํ•„๋“œ)
    • ์—ฌ๊ธฐ์—์„œ๋Š” import static์œผ๋กœ ElementType์„ ์„ ์–ธํ–ˆ์Šต๋‹ˆ๋‹ค.

 

Constraints Annotation์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š” 3๊ฐ€์ง€ ์†์„ฑ

  • message : ์ œ์•ฝ ์กฐ๊ฑด์— ๋งŒ์กฑํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ธฐ๋ณธ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค.
  • groups : ์ œ์•ฝ ์กฐ๊ฑด ๊ทธ๋ฃน์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ์ถ”ํ›„์— ๊ฒ€์‚ฌ ์ˆœ์„œ๋ฅผ ์ง€์ •ํ•˜๋Š” ๊ฒƒ๊ณผ ์—ฐ๊ด€์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์œผ๋กœ Class<?> ๋ฐฐ์—ด๋กœ ์ •์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • payload : client์—์„œ payload ๊ฐ์ฒด๋ฅผ ์œ„ํ•ด ์“ฐ์ž…๋‹ˆ๋‹ค.

payload : ์ „์†ก๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•  ๋•Œ, ํ—ค๋”์™€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ, ์—๋Ÿฌ ์ฒดํฌ ๋น„ํŠธ ๋“ฑ๊ณผ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ์š”์†Œ๋“ค์„ ํ•จ๊ป˜ ๋ณด๋‚ด ๋ฐ์ดํ„ฐ ์ „์†ก์˜ ํšจ์œจ๊ณผ ์•ˆ์ •์„ฑ์„ ๋†’ํžˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ, ๋ณด๋‚ด๊ณ ์ž ํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ž์ฒด๋ฅผ payload๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

 

NameConstraintValidator.java(Validator)

public class NameConstraintValidator implements ConstraintValidator<NameConstraint, String> {
	public static final Set<String> names = new HashSet<>(Arrays.asList("Splin"));

	@Override
	public boolean isValid(String value, ConstraintValidatorContext context) {
		return value != null && names.contains(value);
	}

}

@NameConstraint๋ฅผ ์ฒ˜๋ฆฌํ•ด์ฃผ๋ ค๋ฉด ๋ฐ˜๋“œ์‹œ ConstraintValidator๋ฅผ ๊ตฌํ˜„ํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

ConstraintValidator<NameConstraint, String>๋ฅผ ๋ณด๋ฉด, NameConstraint์—์„œ String ๊ฐ์ฒด์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ•  ๊ฒƒ์ž„์„ ์„ค์ •ํ–ˆ๊ณ , isValid ๋ฉ”์„œ๋“œ์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ Validator๊นŒ์ง€ ๋งŒ๋“ค์–ด ์ฃผ๊ฒŒ ๋˜๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด UserDTO์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ณ , Splin์ด ์•„๋‹Œ ๋‹ค๋ฅธ ๊ฐ’์„ ๋„ฃ์–ด์ฃผ๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

public class UserDTO {
    @NotNull
    private Long id;
    @NotEmpty(message = "fail")
    private String password;
    @NameConstraint
    private String name;
    @Valid
    private Address address;
}

 


์ฐธ๊ณ  : https://bbbicb.tistory.com/43

https://woowacourse.github.io/javable/post/2020-09-20-validation-in-spring-boot/

https://jeong-pro.tistory.com/203

https://velog.io/@damiano1027/Spring-Valid-Validated%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%A6%9D

https://victorydntmd.tistory.com/179

https://otrodevym.tistory.com/entry/Spring-%EC%9C%A0%ED%9A%A8%EC%84%B1-%EA%B2%80%EC%82%AC

https://knight76.tistory.com/entry/javaxvalidationValidationException-HV000183-Unable-to-initialize-javaxelExpressionFactory-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0

https://m.blog.naver.com/varkiry05/222058714706

https://velog.io/@kwj1270/%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98#documented

'Back-End > Spring' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Spring] MapStruct์˜ ์‚ฌ์šฉ๋ฒ• ๋ฐ ModelMapper์™€์˜ ๋น„๊ต  (0) 2023.04.18

๋Œ“๊ธ€