๐ŸŽบ

Validation

 
 
  • ๊ฒ€์ฆ ๋กœ์ง์€ ํŠน์ • ๊ณ„์ธต(์ปจํŠธ๋กค๋Ÿฌ, ์„œ๋น„์Šค, ๋„๋ฉ”์ธ)์— ์ข…์†๋˜๊ธฐ๋ณด๋‹ค๋Š” ๋„๋ฉ”์ธ ์˜ค๋ธŒ์ ํŠธ์ฒ˜๋Ÿผ ๋…๋ฆฝ์ ์œผ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์ข‹์Œ
  • Validator๋ฅผ ์จ์„œ ๊ฒ€์ฆ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋Š” ๊ณณ์„ ์–ด๋””๋กœ ํ• ์ง€๋Š” ๊ณ ๋ฏผํ•ด๋ณด์•„์•ผ ํ•จ. ๋„ค๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์กด์žฌ โ€” by ํ† ๋น„์˜ ์Šคํ”„๋ง
    • ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์†Œ๋“œ ๋‚ด์˜ ์ฝ”๋“œ(Validator๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•˜๊ณ  validate() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์ € ๊ฒ€์ฆ์ž‘์—… ์ง„ํ–‰)
    • @Valid๋ฅผ ์ด์šฉํ•œ ์ž๋™๊ฒ€์ฆ(์ปจํŠธ๋กค๋Ÿฌ์—์„œ) โ€” JSR-303(Bean Validation) ์ด์šฉ
    • ์„œ๋น„์Šค ๊ณ„์ธต ์˜ค๋ธŒ์ ํŠธ์—์„œ์˜ ๊ฒ€์ฆ
      • ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์„œ๋น„์Šค ๊ณ„์ธต ์˜ค๋ธŒ์ ํŠธ์—์„œ ๋ฐ˜๋ณต์ ์œผ๋กœ ๊ฐ™์€ ๊ฒ€์ฆ ๊ธฐ๋Šฅ์ด ์‚ฌ์šฉ๋œ๋‹ค๋ฉด Validator๋กœ ๊ฒ€์ฆ ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  ์ด๋ฅผ DI ๋ฐ›์•„์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
    • ์„œ๋น„์Šค ๊ณ„์ธต์„ ํ™œ์šฉํ•˜๋Š” Validator
      • Validator๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•ด์„œ ์„œ๋น„์Šค ๊ณ„์ธต์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•ด ๊ฒ€์ฆ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค
 

๋ ˆ์ด์–ด ๋ณ„ Validation ์ค‘์š”์„ฑ(Jackson ์˜๊ฒฌ)

  • validation์€ ๋„๋ฉ”์ธ > ์„œ๋น„์Šค > ์ปจํŠธ๋กค๋Ÿฌ ์ˆœ์œผ๋กœ ๋ ˆ์ด์–ด ๋‚ฎ์€ ๊ณณ์—์„œ ๋” ๋งŽ์ด, ์ค‘์š”ํ•˜๊ฒŒ ์ง„ํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค!
    • ์ค‘์š”ํ•œ ๊ฒƒ์€ ์–ด๋– ํ•œ validation ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€๊ฐ€ ์•„๋‹Œ validation ์ด ํ•„์š”ํ•œ ๊ณณ์— ์ ์ ˆํ•˜๊ฒŒ validation ์„ ์ˆ˜ํ–‰ํ–ˆ๋Š”๊ฐ€ ์ž…๋‹ˆ๋‹ค. validation ์ด ์–ด๋””์— ์œ„์น˜ํ•ด์•ผ ํ•˜๋Š”๊ฐ€? ์— ๋Œ€ํ•œ ์งˆ๋ฌธ์— ์‚ฌ๋žŒ๋งˆ๋‹ค ์˜๊ฒฌ์ด ๋‹ค๋ฅผ ์ˆœ ์žˆ์ง€๋งŒ ๊ฐœ์ธ์  ์˜๊ฒฌ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋„๋ฉ”์ธ ๋ชจ๋ธ > ์„œ๋น„์Šค ๋ ˆ์ด์–ด > ์ปจํŠธ๋กค๋Ÿฌ ๋ ˆ์ด์–ด ์˜ˆ๋ฅผ ๋“ค์–ด Email ํด๋ž˜์Šค์˜ String getAddress() ๋ฉ”์†Œ๊ฐ€ null์„ ๋ฆฌํ„ดํ•˜์ง€ ์•Š์Œ์„ ๋ณด์žฅํ•˜๋Š” ๊ฐ€์žฅ ํ™•์‹คํ•œ ๋ฐฉ๋ฒ•์€ ๋ฌด์—‡์„๊นŒ์š”? Email ํด๋ž˜์Šค๋ฅผ ๋ถˆ๋ณ€๊ฐ์ฒด๋กœ ๋งŒ๋“ค๊ณ  address ๋ฅผ ์ƒ์„ฑ์ž์—์„œ ์ฒดํฌํ•˜๋Š” ๊ฒƒ ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ๊ฐ์ฒด์˜ ์ƒ์„ฑ์ž๋Š” ๊ฐ์ฒด๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ํ•„๋“œ๋“ค์— ๋Œ€ํ•œ validation ์„ ์ˆ˜ํ–‰ํ•˜๋Š”๋ฐ ๊ฐ€์žฅ ์ตœ์ ์˜ ์œ„์น˜์ž…๋‹ˆ๋‹ค. ์ƒ์„ฑ์ž์—์„œ ์˜ฌ๋ฐ”๋ฅธ validation ์ˆ˜ํ–‰์„ ๋ณด์ฆํ• ์ˆ˜ ์—†๋‹ค๋ฉด getter ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ๋ฐ˜ํ™˜๊ฐ’์— ๋Œ€ํ•ด ํ•ญ์ƒ ๋ฐฉ์–ด์ ์ธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ UserService ํด๋ž˜์Šค์˜ login ์ด๋ผ๋Š” ๋ฉ”์†Œ๋“œ๋Š” ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ์œ„์น˜์—์„œ๋„ ํ˜ธ์ถœ๋ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํŠน์„ฑ์„ ๊ณ ๋ คํ–ˆ์„ ๋•Œ, validation ๋กœ์ง์˜ ์žฌํ™œ์šฉ์„ฑ์„ ๋†’์ด๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ปจํŠธ๋กค๋Ÿฌ๋ณด๋‹ค๋Š” ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ validation ์ฒ˜๋ฆฌ๊ฐ€ ๋” ์ข‹์Šต๋‹ˆ๋‹ค.
  • ๋งŒ๋“ค๋ฉด์„œ ๋ฐฐ์šฐ๋Š” ํด๋ฆฐ์•„ํ‚คํ…์ฒ˜์™€ ์ด๋•Œ๊นŒ์ง€์˜ ๊ฒฝํ—˜์„ ์ข…ํ•ฉํ•ด์„œ ์ •๋ฆฌ๋ฅผ ํ•ด๋ณด๋ฉด
    • ์ปจํŠธ๋กค๋Ÿฌ - ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์ฆ
      • ์„œ๋น„์Šค์˜ ์ž…๋ ฅ๋ชจ๋ธ์€ ์„œ๋น„์Šค์˜ ๋งฅ๋ฝ์—์„œ ์œ ํšจํ•œ ์ž…๋ ฅ๋งŒ ํ—ˆ์šฉ. ์ปจํŠธ๋กค๋Ÿฌ์—์„œ์˜ ์ž…๋ ฅ ๋ชจ๋ธ์€ ์„œ๋น„์Šค์˜ ์ž…๋ ฅ ๋ชจ๋ธ๊ณผ๋Š” ๊ตฌ์กฐ๋‚˜ ์˜๋ฏธ๊ฐ€ ์™„์ „ํžˆ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ
      • ์„œ๋น„์Šค ์ž…๋ ฅ ๋ชจ๋ธ์—์„œ ํ–ˆ๋˜ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ๋˜‘๊ฐ™์ด ์ปจํŠธ๋กค๋Ÿฌ์—์„œ๋„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๊ณ , ์ปจํŠธ๋กค๋Ÿฌ์˜ ์ž…๋ ฅ ๋ชจ๋ธ์„ ์„œ๋น„์Šค์˜ ์ž…๋ ฅ ๋ชจ๋ธ๋กœ ๋ณ€ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๊ฒ€์ฆ
      • (๊ทธ๋Ÿผ, ์ปจํŠธ๋กค๋Ÿฌ์™€ ์„œ๋น„์Šค์—์„œ ๊ฐ™์€ ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์€ ํ•œ๋ฒˆ๋งŒ ํ•˜๋ฉด ๋˜๊ฒ ๊ตฌ๋‚˜)
    • ์„œ๋น„์Šค - ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์ฆ, ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ๊ฒ€์ฆ
      • ์ปจํŠธ๋กค๋Ÿฌ์˜ ์›น ๋ชจ๋ธ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์€ ์ปจํŠธ๋กค๋Ÿฌ ์›น ๋ชจ๋ธ์—์„œ ์ง„ํ–‰๋˜๊ณ , ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ๊ฒ€์ฆ๋งŒ ํ•˜๋ฉด ๋จ
    • ๋„๋ฉ”์ธ - ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ๊ฒ€์ฆ & ์ƒ์„ฑ์ž๋กœ ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์ฆ
      • ์„œ๋น„์Šค์—์„œ ์ง„ํ–‰ํ•˜๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ๊ฒ€์ฆ์„ ๋„๋ฉ”์ธ ๋กœ์ง์œผ๋กœ ์ฒ˜๋ฆฌํ•ด๋„ ๋จ
      • ๋„๋ฉ”์ธ์ด ์ œ์ผ ์•ˆ์ชฝ์— ์žˆ์œผ๋‹ˆ ์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์€ ์ค‘์š”ํ•˜๊ฒŒ ์ง„ํ–‰๋˜์–ด์•ผ ํ•จ!

์ž…๋ ฅ ์œ ํšจ์„ฑ ๊ฒ€์ฆ

  • ๊ฒ€์ฆํ•ด์•ผ ํ•  ๊ฐ’์ด ๋งŽ์€ ๊ฒฝ์šฐ ์ฝ”๋“œ์˜ ๊ธธ์ด๊ฐ€ ๊ธธ์–ด์ง„๋‹ค.
  • ๊ตฌํ˜„์— ๋”ฐ๋ผ์„œ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ Service Logic๊ณผ์˜ ๋ถ„๋ฆฌ๊ฐ€ ํ•„์š”ํ•จ
  • ํฉ์–ด์ ธ ์ž‡๋Š” ๊ฒฝ์šฐ ์–ด๋””์—์„œ ๊ฒ€์ฆ์„ ํ•˜๋Š”์ง€ ์•Œ๊ธฐ ์–ด๋ ค์šฐ๋ฉฐ, ์žฌ์‚ฌ์šฉ์˜ ํ•œ๊ณ„๊ฐ€ ์žˆ์Œ
  • ๊ตฌํ˜„์— ๋”ฐ๋ผ ๋‹ฌ๋ผ ์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ฒ€์ฆ Logic์ด ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋“ฑ ์ฐธ์กฐํ•˜๋Š” ํด๋ž˜์Šค์—์„œ Logic์ด ๋ณ€๊ฒฝ๋˜์–ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„์ด ๋ฐœ์ƒ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • implementation โ€˜org.springframework.boot:spring-boot-starter-validationโ€™
notion image

Bean Validation 2.0 (JSR 380)

[Baeldung] Java Bean Validation Basics โ€” JSR(Java Specification Request) 380 (Bean Validation 2.0)
implementation('org.springframework.boot:spring-boot-starter-validation') // ํ˜น์€ hibernate-validator ๋งŒ ์ถ”๊ฐ€ํ•ด๋„ ๋จ. ๊ทผ๋ฐ ์ด ๋•Œ spring boot ๋ฒ„์ „์ด๋ž‘ ์•ˆ๋งž์œผ๋ฉด ๋™์ž‘์„ ์•ˆํ•˜๋”๋ผ implementation 'org.hibernate.validator:hibernate-validator:6.2.0.Final'
  • Bean Validation 2.0 ์€ JSR-380 ์œผ๋กœ ๋ถˆ๋ฆฌ๋Š” api์— ๋Œ€ํ•œ ์ •์˜
  • Hibernate Validator ๋Š” ๊ทธ๊ฒƒ์— ๋Œ€ํ•œ ๊ตฌํ˜„์ž„. 2018.4์›” ๊ธฐ์ค€์œผ๋กœ ์œ ์ผํ•œ jSR-380 ์—๋Œ€ํ•œ ์ธ์ฆ๋ฐ›์€ ๊ตฌํ˜„

Request Body ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ (@Valid)

@RestController public class ValidateRequestBodyController { @PostMapping("/validateBody") ResponseEntity<String> validateBody(@Valid @RequestBody InputRequest request) { return ResponseEntity.ok("valid"); } }
  • @Valid ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์Œ์œผ๋กœ Spring์ด ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์ „์— ๋จผ์ € ๊ฐ์ฒด๋ฅผ Validator์— ์ „๋‹ฌํ•ด์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ฒŒ ๋จ
  • ๋งŒ์•ฝ InputRequest์•ˆ์— ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•ด์•ผ ํ•˜๋Š” ๋‹ค๋ฅธ ๊ฐ์ฒด๊ฐ€ ํ•„๋“œ๋กœ ํฌํ•จ๋˜๋Š” ๊ฒฝ์šฐ ๊ทธ ๊ฐ์ฒด์—๋„ @Valid๋ฅผ ๋ถ™์—ฌ์ค˜์•ผ ํ•จ โ€” Complex Type
  • @RequestBody์•ž์— @Valid๋ฅผ ๋ถ™์ด๊ณ  InputRequest ํ•„๋“œ๋“ค์— ๋Œ€ํ•ด @Min, @Max ์ด๋Ÿฐ ์• ๋“ค ๋ถ™์ด๋ฉด ๋จ
  • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‹คํŒจํ•  ๊ฒฝ์šฐ MethodArgumentNotValidException ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒ
  • Kotlin์—์„œ๋Š” @field:NotBlank ์™€ ๊ฐ™์€ ์‹์œผ๋กœ ์ ์šฉํ•ด์•ผํ•จ [ ์ฐธ๊ณ  ]

@RequestParam (์ฟผ๋ฆฌํŒŒ๋ผ๋ฏธํ„ฐ) & @PathVariable (path param) ์— ๋Œ€ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์ฆ

@Validated @RestController public class ValidateParametersController { @GetMapping("/validatePathVariable/{id}") ResponseEntity<String> validatePathVariable(@PathVariable("id") @Min(5) int id) { return ResponseEntity.ok("valid"); } @GetMapping("/validateRequestParameter") ResponseEntity<String> validateRequestParameter(@RequestParam("param") @Min(5) int param) { return ResponseEntity.ok("valid"); } }
  • @Validated ์–ด๋…ธํ…Œ์ด์…˜์„ ํด๋ž˜์Šค ๋ ˆ๋ฒจ์˜ Controller์— ์ถ”๊ฐ€ํ•ด Spring์ด ๋ฉ”์„œ๋“œ ๋งค๊ฐœ ๋ณ€์ˆ˜์— ๋Œ€ํ•œ ์ œํ•œ ์กฐ๊ฑด annotation์„ ํ‰๊ฐ€ํ•˜๊ฒŒ ํ•ด์•ผํ•œ๋‹ค.
  • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ์‹คํŒจํ•  ๊ฒฝ์šฐ ConstraintViolationException ๋ฐœ์ƒ
 

@Valid vs @Validated

  • @Valid
    • method level validation
    • member attribute for validation ์—์„œ๋„ ์‚ฌ์šฉํ•จ
    • ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์‹คํŒจ์‹œ โ†’ MethodArgumentNotValidException
  • @Validated
    • group level validation์—์„œ ์‚ฌ์šฉ (ํด๋ž˜์Šค ์ˆ˜์ค€์—์„œ ํ‰๊ฐ€๋จ)
    • ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์‹คํŒจ์‹œ โ†’ ConstraintViolationException
 

@BindingResult

@PostMapping public ResponseEntity post(@Valid @RequestBody User user, BindingResult bindingResult){ if(bindingResult.hasErrors()){ StringBuilder sb = new StringBuilder(); bindingResult.getAllErrors().forEach(objectError->{ FieldError field = (FieldError) objectError; String message = objectError.getDefaultMessage(); sb.append("field : " + field.getField()); sb.append("message : " + message); }); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(sb.toString()); } //logic return ResponseEntity.ok(user); } public class User{ private String name; private int age; @Email private String email; @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message="ํ•ธ๋“œํฐ ๋ฒˆํ˜ธ ์–‘์‹๊ณผ ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค.") private String phoneNumber; ... }
  • BindingResult๋ผ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ•จ์ˆ˜์—์„œ ๋ฐ›์Œ์œผ๋กœ ๋ณ€์ˆ˜์˜ ํ˜•ํƒœ์— ๋งž๊ฒŒ ๊ฐ’์ด ๋“ค์–ด์˜ค์ง€ ์•Š์•˜์„๋•Œ ์•„์˜ˆ ์—๋Ÿฌ๋ฅผ ๋‚ด๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์–ด๋–ค ์‹์œผ๋กœ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋Š”์ง€๋ฅผ ๋ฐ˜ํ™˜ ๊ฐ€๋Šฅํ•จ โ†’ ์ด๊ฒƒ๋ณด๋‹ค๋Š” exception ์ฒ˜๋ฆฌ๊ฐ€ ๋‚˜์„๋“ฏ

Validation ์–ด๋…ธํ…Œ์ด์…˜ ์ข…๋ฅ˜

  • @Past : annotated element must be an instant, date or time in the past.
  • @Future : annotated element must be an instant, date or time in the future.
 

Validation ์ปค์Šคํ…€ํ•˜๊ฒŒ ํ•˜๊ธฐ

์Šคํ”„๋ง์ด ์ œ๊ณตํ•˜๋Š” Validator ๋ฅผ ์“ฐ๋˜, valid ํ›„ exception์„ ์ปค์Šคํ…€ํ•˜๊ฒŒ ๋˜์ง€๊ธฐ

public static void validate(ReserveTownHallRequest request) { ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); Validator validator = validatorFactory.getValidator(); Set<ConstraintViolation<ReserveTownHallRequest>> violations = validator.validate(request); if (!violations.isEmpty()) { for (ConstraintViolation<ReserveTownHallRequest> violation: violations) { Path propertyPath = violation.getPropertyPath(); if (propertyPath.toString().equals("name")) { throw new TownHallCreateException(ErrorCode.TOWNHALL_TITLE_IS_EMPTY); } } throw new ConstraintViolationException(violations); } }
  • ์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•˜๋Š” Validator๋ฅผ ๊ฐ€์ ธ์˜จ ๋’ค, ์ง์ ‘ violations๋ฅผ ํ•˜๋‚˜์”ฉ ๋ณด๋ฉด์„œ ํ•ด๋‹นํ•˜๋Š” field ์— ๋Œ€ํ•ด์„œ exception์„ ์ปค์Šคํ…€ํ•˜๊ฒŒ throw

AssertTrue/False๋ฅผ ์ด์šฉํ•œ Validation

@AssertTrue(message= "yyyyMM์˜ ํ˜•์‹์— ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค.") public boolean isreqYearMonth(){ try{ LocalDate localDate = LocalDate.parse(this.reqYearMonth + "01", DateTimeFormatter.ofPattern("yyyyMMdd")); } catch(Exception e){ return false; } return true; }
  • @AssertTrue annotation์ด ๋ถ™์€ ๋ฉ”์จ๋“œ๋Š” is๋กœ ์‹œ์ž‘ํ•ด์•ผ ๋™์ž‘์ด ๋จ
  • ๊ทผ๋ฐ ์œ„์™€ ๊ฐ™์ด ์ž‘์„ฑํ•  ๋•Œ์˜ ๋ฌธ์ œ์ ์€ class ๋ณ„๋กœ ์ € ๋ฉ”์จ๋“œ๋ฅผ ๋‹ค ๋งŒ๋“ค์–ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ โ†’ Custom Annotation์„ ๋งŒ๋“ค์ž!

Constraint Validator๋ฅผ ์ด์šฉํ•œ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ Validation

//YearMonth.java - annotation @Constraint(validatedBy = { YearMonthValidator.class}) @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) public @interface YearMonth{ String message() default "yyyyMM ํ˜•์‹์— ๋งž์ง€ ์•Š์Šต๋‹ˆ๋‹ค." Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; String pattern() default "yyyyMM"; } //YearMonthValidator.java public class YearMonthValidator implements ConstraintValidator<YearMonth, String>{ private String pattern; @Override public void initialize(YearMonth constraintAnnotation){ this.pattern = constraintAnnotation.pattern() + "dd"; } @Override public boolean isValid(String value, ConstraintValidatorContext context){ // yyyyMM try{ LocalDate localDate = LocalDate.parse(value + "01", DateTimeFormatter.ofPattern(this.pattern)); } catch(Exception e){ return false; } return true; } }
  • ์œ„์—์„œ ๋งŒ๋“  Annotation YearMonth๋ฅผ validation ํ•˜๊ณ  ์‹ถ์€ ๋ณ€์ˆ˜์œ„์—๋‹ค๊ฐ€ ๋ถ™์ด๊ธฐ
 

๋ฌธ์ž์—ด ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์œ ํ‹ธ ๋ฉ”์„œ๋“œ StringUtils

[์ฐธ๊ณ ] link

๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ๊ฒ€์ฆ

  • ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์— ๋Œ€ํ•œ ๊ฒ€์ฆ์€ ๋„๋ฉ”์ธ ์—”ํ‹ฐํ‹ฐ ์•ˆ์— ์œ„์น˜ํ•˜๊ฑฐ๋‚˜, ์„œ๋น„์Šค ์ฝ”๋“œ์—์„œ ๋„๋ฉ”์ธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „์— ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ
public class Account{ public boolean withdraw(Money money, AccountId targetAccountId) { if (!mayWithdraw(money)){ return false; } // ... } }
๋„๋ฉ”์ธ์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™ ๊ฒ€์ฆ
public class SendMoneyService implement SendMoneyUseCase{ // ... @Override public boolean sendMoney(SendMoneyCommand command) { requireAccountExists(command.getSourceAccountId()); requireAccountExists(command.getTargetAccountId());