[TIL] 2025-01-31_@Valid

Yuriยท2025๋…„ 1์›” 31์ผ

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
39/59

๐Ÿ”ซ ์ผ์ •๊ด€๋ฆฌ API ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉฐ ๊ฒช์€ ๋ฌธ์ œ์ ๊ณผ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•, ์ƒˆ๋กœ ์•Œ๊ฒŒ๋œ ์ ์„ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“‹ ๊ธฐํš ์ •์˜

null ์ฒดํฌ ๋ฐ ํŠน์ • ํŒจํ„ด์— ๋Œ€ํ•œ ๊ฒ€์ฆ ์ˆ˜ํ–‰

  • ์„ค๋ช…
    • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
      1. ์ž˜๋ชป๋œ ์ž…๋ ฅ์ด๋‚˜ ์š”์ฒญ์„ ๋ฏธ๋ฆฌ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
      2. ๋ฐ์ดํ„ฐ์˜ย ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•˜๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์˜ˆ์ธก ๊ฐ€๋Šฅ์„ฑ์„ ๋†’์—ฌ์ค๋‹ˆ๋‹ค.
      3. Spring์—์„œ ์ œ๊ณตํ•˜๋Š”ย @Validย ์–ด๋…ธํ…Œ์ด์…˜์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์กฐ๊ฑด
    • ํ• ์ผ์€ ์ตœ๋Œ€ 200์ž ์ด๋‚ด๋กœ ์ œํ•œ, ํ•„์ˆ˜๊ฐ’ ์ฒ˜๋ฆฌ
    • ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜๊ฐ’ ์ฒ˜๋ฆฌ
    • ๋‹ด๋‹น์ž์˜ ์ด๋ฉ”์ผ ์ •๋ณด๊ฐ€ ํ˜•์‹์— ๋งž๋Š”์ง€ ํ™•์ธ

@Valid

๊ฐ์ฒด์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜
์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•œ ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

โ˜‘๏ธ Hibernate Validator ๊ณต์‹๋ฌธ์„œ
๐Ÿ“‹ ๋ฒ„์ „๋ณ„ ๊ณต์‹ ๋งค๋‰ด์–ผ

์ฃผ์š”๊ธฐ๋Šฅ

  • ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ: @Valid ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ๊ฐ์ฒด๋‚˜ ๊ทธ ์†์„ฑ์— ๋Œ€ํ•ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ๊ทœ์น™์„ ์ ์šฉํ•œ๋‹ค. ๊ฐ์ฒด์— ์ •์˜๋œ ์ œ์•ฝ์กฐ๊ฑด (@NotNull, @Size, @Min, @Max ๋“ฑ)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฒ€์‚ฌํ•œ๋‹ค.
  • ์ž๋™ ๊ฒ€์‚ฌ: Spring์—์„œ๋Š” @Valid๊ฐ€ ๋ถ™์€ ๊ฐ์ฒด๊ฐ€ ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋“ค์–ด์˜ค๋ฉด, ํ•ด๋‹น ๊ฐ์ฒด๊ฐ€ ์ž๋™์œผ๋กœ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ๋ฐ›๋Š”๋‹ค. ์ฃผ๋กœ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ HTTP ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ”์ธ๋”ฉํ•  ๋•Œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋œ๋‹ค.

์˜์กด์„ฑ ์ฃผ์ž…

build.gradle์˜ dependencies์— implementation 'org.springframework.boot:spring-boot-starter-validation' ์ถ”๊ฐ€

์‚ฌ์šฉ์˜ˆ์‹œ

  1. DTO ํด๋ž˜์Šค์—์„œ ์ œ์•ฝ ์กฐ๊ฑด ์„ค์ •
@Getter
public class ScheduleRequestDto {

    private Long authorId;
    private String author;
    @NotNull(message = "ํ• ์ผ์€ ํ•„์ˆ˜๊ฐ’์ž…๋‹ˆ๋‹ค.")
    @Size(max = 200, message = "ํ• ์ผ์€ ์ตœ๋Œ€ 200์ž ์ดํ•˜๋กœ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.")
    private String todo;
    @NotNull(message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜๊ฐ’์ž…๋‹ˆ๋‹ค.")
    private String password;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date modDate;

}
  • @Size: ๋ฌธ์ž์—ด, ๋ฐฐ์—ด, ๋ฆฌ์ŠคํŠธ ๋“ฑ์—์„œ ๊ธธ์ด(ํฌ๊ธฐ) ์กฐ๊ฑด ๋ช…์‹œ
    • ๊ธธ์ด๊ฐ€ ์ง€์ •๋œ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋ฉด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์—์„œ ์˜ค๋ฅ˜ ๋ฐœ์ƒ
    • ๋ฌธ์ž์—ด์˜ ๊ธธ์ด๋ฅผ ์ œํ•œํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์œ ์šฉํ•œ ์–ด๋…ธํ…Œ์ด์…˜
      • min: ํ—ˆ์šฉ๋˜๋Š” ์ตœ์†Œ ํฌ๊ธฐ. ๊ธฐ๋ณธ๊ฐ’์€ 0
      • max: ํ—ˆ์šฉ๋˜๋Š” ์ตœ๋Œ€ ํฌ๊ธฐ. ๊ธฐ๋ณธ๊ฐ’์€ Integer.MAX_VALUE
      • message: ๊ฒ€์ฆ ์‹คํŒจ ์‹œ ๋ฐ˜ํ™˜๋  ์˜ค๋ฅ˜ ๋ฉ”์„ธ์ง€ ์„ค์ •
    • Spring ์—์„œ๋Š” @Valid๋‚˜ @Validated ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด์•ผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • @NotNull: ํ•ด๋‹น ํ•„๋“œ๊ฐ€ null ์ด ์•„๋‹ˆ์–ด์•ผ ํ•œ๋‹ค๋Š” ์กฐ๊ฑด ๋ช…์‹œ
    ๋นˆ ๋ฌธ์ž์—ด์ด๋‚˜ 0, false ๋“ฑ์€ ํ—ˆ์šฉ๋˜์ง€๋งŒ, null ์€ ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค.

@NotNull ๊ณผ @NotEmpty, @NotBlank์˜ ์ฐจ์ด์ 

  • @NotNull: ํ•„๋“œ๊ฐ€ null์ด ์•„๋‹Œ์ง€ ๊ฒ€์ฆ. ๋นˆ ๋ฌธ์ž์—ด("")์€ ํ—ˆ์šฉ.
  • @NotEmpty: ํ•„๋“œ๊ฐ€ null์ด ์•„๋‹ˆ๊ณ , ๋น„์–ด ์žˆ์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค๋Š” ์ œ์•ฝ ์กฐ๊ฑด. @NotEmpty๋Š” ๋ฌธ์ž์—ด์ด๋‚˜ ์ปฌ๋ ‰์…˜์— ์ ์šฉ๋˜๋ฉฐ, ๋นˆ ๋ฌธ์ž์—ด, ๋นˆ ๋ฆฌ์ŠคํŠธ ๋“ฑ๋„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • @NotBlank: ๋ฌธ์ž์—ด ํ•„๋“œ์— ์ ์šฉ๋˜๋ฉฐ, null์ด ์•„๋‹ˆ๊ณ , ๊ณต๋ฐฑ ๋ฌธ์ž์—ด๋„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ฆ‰, " "์™€ ๊ฐ™์€ ๊ณต๋ฐฑ๋งŒ ์žˆ๋Š” ๋ฌธ์ž์—ด์€ ์œ ํšจํ•˜์ง€ ์•Š๋‹ค๊ณ  ํŒ๋‹จํ•œ๋‹ค.
  1. ์ปจํŠธ๋กค๋Ÿฌ์—์„œ @Valid ์‚ฌ์šฉ
@PostMapping
public ResponseEntity<ScheduleResponseDto> saveSchedule(@RequestBody @Valid ScheduleRequestDto dto) {
        return new ResponseEntity<>(scheduleService.saveSchedule(dto), HttpStatus.OK);
    }

๋™์ž‘์›๋ฆฌ

  • @Valid ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ๊ฐ์ฒด๋Š” ํ•ด๋‹น ๊ฐ์ฒด์˜ ํ•„๋“œ์— ์„ค์ •๋œ ์œ ํšจ์„ฑ ์ œ์•ฝ ์กฐ๊ฑด์„ ๊ธฐ์ค€์œผ๋กœ ๊ฒ€์‚ฌ๊ฐ€ ์ˆ˜ํ–‰๋œ๋‹ค.
  • ๊ฒ€์‚ฌ ๊ฒฐ๊ณผ๊ฐ€ ์‹คํŒจํ•˜๋ฉด, MethodArgumentNotValidException ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ , Spring์€ ์ด ์˜ˆ์™ธ๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ 400 (Bad Request) ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

@ExceptionHandler ์ ์šฉ


e.getMessage() ๋ฅผ detail ์— ๋„˜๊ธฐ๋ฉด ๋ณต์žกํ•œ ์—๋Ÿฌ ๋กœ๊ทธ๊ฐ€ ์ถœ๋ ฅ๋œ๋‹ค. ์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ์—๋Ÿฌ ๋กœ๊ทธ๋ฅผ ํ•ด์„ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

  • e.getMessage() ์˜ค๋ฒ„๋ผ์ด๋”ฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฉ”์„ธ์ง€๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค
    : ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ์–ด๋–ค ํ•„๋“œ์— ์ ์šฉ๋œ ์ œ์•ฝ์กฐ๊ฑด ๊ฒ€์‚ฌ๊ฐ€ ์‹คํŒจํ–ˆ๋Š”์ง€๋งŒ ์•Œ๋ฉด ๋œ๋‹ค.

  • ํ•„๋“œ ์ œ์•ฝ์กฐ๊ฑด ์–ด๋…ธํ…Œ์ด์…˜์— ์ž‘์„ฑํ•œ ๋ฉ”์„ธ์ง€ ์ถœ๋ ฅํ•˜๊ธฐ
    โ–ถ๏ธŽ GlobalExceptionHandler.java

	@ExceptionHandler(MethodArgumentNotValidException.class)
    private ResponseEntity<ErrorResponseDto> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        StringBuilder errorMsg = new StringBuilder();
        for (ObjectError error : e.getBindingResult().getAllErrors()) {
            errorMsg.append(error.getDefaultMessage()).append(" ");
        }
        return ErrorResponseDto.errResponseEntity(new CustomException(ErrorCode.INVALID_CONSTRAINTS, errorMsg.toString()));
    }
  • e.getBindingResult().getAllErrors(): BindingResult์—์„œ ์˜ค๋ฅ˜๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, getAllErrors()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ชจ๋“  ์˜ค๋ฅ˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
  • ๊ฐ ์˜ค๋ฅ˜ ๊ฐ์ฒด๋Š” defaultMessage ์†์„ฑ์„ ํ†ตํ•ด ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ์ด๋ฅผ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ ์ ˆํžˆ ํ‘œ์‹œํ•˜๊ฑฐ๋‚˜ ๋กœ๊น…ํ•  ์ˆ˜ ์žˆ๋‹ค.
profile
์•ˆ๋…•ํ•˜์„ธ์š” :)

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