ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [SpringBoot] ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๊ฐ’๊ณผ Request Body๋ฅผ Validationํ•˜๋Š” 2๊ฐ€์ง€ ๋ฐฉ๋ฒ•
    Back-end/TIL 2022. 4. 4. 15:45

    ๐Ÿ“Œ Validation์ด ํ•„์š”ํ•œ ์ด์œ 

    ์˜๋„์ ์œผ๋กœ ์ด์ƒํ•œ ๊ฐ’์œผ๋กœ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ก ํŠธ์™€ ์„œ๋ฒ„์—์„œ ๋‘˜๋‹ค ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

    ํšŒ์‚ฌ ์ฝ”๋“œ๋ฅผ ๋ณด๋‹ˆ ์ด๋ฉ”์ผ์ด๋‚˜ ์ „ํ™”๋ฒˆํ˜ธ ์ž…๋ ฅ ๊ฐ’์— ๋Œ€ํ•œ ํ˜•์‹์„ ๋ชจ๋‘ ์„œ๋ฒ„์—์„œ๋„ ์ฒดํฌํ•ด์ฃผ์—ˆ๋‹ค.

    (๊ธฐ์กด์— ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ ํ•  ๋•Œ๋Š” '์ž…๋ ฅ๊ฐ’์€ ํ”„๋ก ํŠธ์—์„œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ•ด์ฃผ๋‹ˆ๊นŒ ๊ดœ์ฐฎ๊ฒ ์ง€' ํ•˜๊ณ  ๋„˜์–ด๊ฐ”์—ˆ๋Š”๋ฐ ๋ฐ˜์„ฑํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ๋‹ค์Œ ์‚ฌ์ด๋“œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ์„œ๋ฒ„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋„ ์ถ”๊ฐ€ํ•ด์•ผ๊ฒ ๋‹ค)

     

    ๊ทธ๋ ‡๋‹ค๋ฉด, ์–ด์ฐจํ”ผ ์„œ๋ฒ„์—์„œ๋„ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•ด์ฃผ๋Š”๋ฐ ํ”„๋ก ํŠธ์—์„œ๋„ ํ•˜๋Š” ์ด์œ ๋Š”?

    - ์„œ๋ฒ„ ๋ถ€ํ•˜๋ฅผ ์ค„์—ฌ์ฃผ๊ณ , ์‚ฌ์šฉ์ž์˜ ํŽธ์˜์„ฑ์„ ์œ„ํ•ด์„œ์ด๋‹ค.

    - ์„œ๋ฒ„์—์„œ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋Š” ํ”„๋ก ํŠธ์—์„œ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ณด๋‹ค๋Š” ๋Š๋ฆด ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

     

    ๐Ÿ“Œ ์ค€๋น„

    • ์˜์กด์„ฑ ์ถ”๊ฐ€
    dependencies {
    	implementation 'org.springframework.boot:spring-boot-starter-validation'
    }

    ์œ„์˜ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ฉด, Bean Validation ๊ตฌํ˜„์ฒด๋กœ Hibernate Validator๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Spring Boot์—์„œ Validation์„ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

     

    ๐Ÿ“Œ ๋ฐฉ๋ฒ•1 - Hibernate์—์„œ ์ œ๊ณตํ•˜๋Š” javax.validation.constraints(@NotEmpty, @NotNull ๋“ฑ)์˜ ๊ธฐ์กด ๊ตฌํ˜„๋œ ์–ด๋…ธํ…Œ์ด์…˜ ์‚ฌ์šฉ

    @Getter
    @Setter
    @NoArgsConstructor
        publicclass UserSignUpReqDto {
    				...
    
            @NotEmpty(message = "๋‹‰๋„ค์ž„์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
            private String nickName;    //๋‹‰๋„ค์ž„
    
            @NotEmpty(message = "์ด๋ฉ”์ผ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
            @Email
            private String email;   //์ด๋ฉ”์ผ
    
    	      ...
        }

     

    ์œ„์™€ ๊ฐ™์ด ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด ์ ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

     

    @NotEmpty, @NotNull, @NotBlank์˜ ์ฐจ์ด๋Š”?

    • @NotEmpty - null, ""์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. " "๋Š” ํ—ˆ์šฉํ•œ๋‹ค.
    • @NotNull - null๋งŒ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. "", " "๋Š” ํ—ˆ์šฉํ•œ๋‹ค.
    • @NotBlank - null, "", " "์„ ๋ชจ๋‘ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

     

    ๐Ÿ“Œ ๋ฐฉ๋ฒ•2 - ConstraintValidator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด Custom Validation ๊ตฌํ˜„

    Custom Validation ์‚ฌ์šฉ ์ด์œ 

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

     

    ์ฃผ์˜ํ•  ์ 

    • ์–ด๋…ธํ…Œ์ด์…˜์˜ ๋‚ด๋ถ€ ๋กœ์ง์ด ์–ด๋–ค ๋™์ž‘์„ ํ•˜๋Š”์ง€ ์ •ํ™•ํžˆ ๋ชจ๋ฅธ๋‹ค๋ฉด, ํ”Œ๋กœ์šฐ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์–ด๋ ต๋‹ค.
    • ๋ฐ˜๋ณต์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ , ํŠน์ • ์š”์ฒญ์—๋งŒ ์‚ฌ์šฉํ•˜๋Š” ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์˜ ๊ฒฝ์šฐ์—๋Š” ์ด๊ฒƒ ๋Œ€์‹  ๋‹จ์ˆœ ๋ฉ”์„œ๋“œ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฒŒ ์ข‹๋‹ค.

     

    ๊ตฌํ˜„ ๋ฐฉ๋ฒ•

    • ๊ธฐ์กด์— ๊ตฌํ˜„๋˜์–ด์žˆ๋Š” @NotEmpty๋‚˜ @NotNull๊ณผ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ๋œ๋‹ค.
    • ์ถ”๊ฐ€ํ•ด์•ผํ•  ํŒŒ์ผ์€ ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜ interface์™€ ConstraintValidator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ์ปค์Šคํ…€ Validator ํด๋ž˜์Šค ์ด 2๊ฐ€์ง€ ์ด๋‹ค.

     

    ์‹ค์Šต

    ํšŒ์›๊ฐ€์ž… ์ •๋ณด ์ž…๋ ฅ ์‹œ ์ „ํ™”๋ฒˆํ˜ธ์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ ํ˜•์‹์„ ์ฒดํฌํ•ด์ฃผ๋Š” custom validator๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.

     

    ๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป ์‹ค์Šต ์ฝ”๋“œ ํ™•์ธ : spring-validation-practice

     

    A. ์ปค์Šคํ…€ ์–ด๋…ธํ…Œ์ด์…˜ interface

    // SignUpDtoCheck interface
    @Constraint(validatedBy = SignUpDtoCheckValidator.class)
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SignUpDtoCheck {
    
        String message() default "SignUpDtoCheck is invalid";
    
        Class<?>[] groups() default { };
    
        Class<? extends Payload>[] payload() default { };
    }
    
    • @Constraint
      • validatedBy์˜ ๊ฐ’์— ConstraintValidator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ์ปค์Šคํ…€ Validator ํด๋ž˜์Šค๋ฅผ ์ง€์ •ํ•ด์ค€๋‹ค. ์‹ค์งˆ์ ์ธ ๊ฒ€์ฆ์ด ์ด๋ค„์ง€๋Š” ํด๋ž˜์Šค์ด๋‹ค.
    • @Target
      • ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์ด ์ ์šฉ๋  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
      • ElementType.TYPE์˜ ๊ฒฝ์šฐ ํด๋ž˜์Šค, ์ธํ„ฐํŽ˜์ด์Šค, Enum์— ์ ์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค.
    • @Retention
      • ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜์˜ ๋ผ์ดํ”„ ์‚ฌ์ดํด์„ ์ง€์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
    • @interface
      • ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์˜๋ฏธํ•œ๋‹ค.

     

    B. ConstraintValidator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ์ปค์Šคํ…€ Validator ํด๋ž˜์Šค

    public class SignUpDtoCheckValidator implements ConstraintValidator<SignUpDtoCheck, UserDto.UserSignUpReqDto> {
    
        @Override
        public void initialize(SignUpDtoCheck constraintAnnotation) {
    
        }
    
        @Override
        public boolean isValid(UserDto.UserSignUpReqDto value, ConstraintValidatorContext context) {
    
            int invalidCount = 0;
    
            PatternUtils patternUtils = new PatternUtils();
    
            if(!patternUtils.phoneNumberMatch(value.getPhoneNumber())) {
                addConstraintViolation(context, "์ „ํ™”๋ฒˆํ˜ธ๋Š” ์ˆซ์ž 8~11์ž๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.", "phoneNumber");
                invalidCount++;
            }
    
            if (!patternUtils.passwordMatch(value.getPassword())) {
                addConstraintViolation(context, "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์˜๋ฌธ์ž 4~6์ž๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.", "password");
                invalidCount++;
            }
    
            return invalidCount == 0;
        }
    
        private void addConstraintViolation(ConstraintValidatorContext context, String errorMessage, String firstNode) {
            context.disableDefaultConstraintViolation()
            context.buildConstraintViolationWithTemplate(errorMessage)
                    .addPropertyNode(firstNode)
                    .addConstraintViolation();
        }
    
    }
    
    • ConstraintValidator ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” initialize()์™€ isValid() ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋”ฉ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
    • isValid() ๋‚ด๋ถ€์— ๊ฒ€์ฆ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์—ฌ boolean์œผ๋กœ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.
    • addConstraintViolation()
      • disableDefaultConstraintViolation() - SignUpDtoCheck ์ธํ„ฐํŽ˜์ด์Šค์— ์„ค์ •ํ•ด๋‘” default message ๋Œ€์‹  ๋‹ค๋ฅธ ๋‚ด์šฉ์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•œ๋‹ค.
      • ๊ตฌ์ฒด์ ์ธ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์™€ ๊ฒ€์ฆํ•œ Node Key๊ฐ’์„ ๋„˜๊ฒจ์ค€๋‹ค. ํ•ด๋‹น Node๋Š” Errors ๊ฐ์ฒด์˜ errors ๋ฆฌ์ŠคํŠธ์— ์ €์žฅ๋  ์›์†Œ์˜ field ์†์„ฑ์— ๋ฐ”์ธ๋”ฉ๋œ๋‹ค.

     

    C. Validation ์ ์šฉ

    @Getter @Setter
    @NoArgsConstructor
    @SignUpDtoCheck //์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด ์ ์šฉ
    public static class UserSignUpReqDto {
    
            @NotEmpty(message = "๋‹‰๋„ค์ž„์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
            private String nickName;    //๋‹‰๋„ค์ž„
    
            private String phoneNumber; //์ „ํ™”๋ฒˆํ˜ธ
    
            @NotEmpty(message = "์ด๋ฉ”์ผ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
            @Email
            private String email;   //์ด๋ฉ”์ผ
    
            private String password;    //๋น„๋ฐ€๋ฒˆํ˜ธ
    
            @Builder
            public UserSignUpReqDto(String nickName, String phoneNumber, String email, String password) {
                this.nickName = nickName;
                this.phoneNumber = phoneNumber;
                this.email = email;
                this.password = password;
            }
     }
    
    //Controller
    @PostMapping("/user/sign-up")
    public String userSignUp(@Valid UserDto.UserSignUpReqDto userSignUpReqDto, Errors errors, Model model) {
            //ํšŒ์›๊ฐ€์ž… ์‹คํŒจ
            if (errors.hasErrors()) {
    
                return "user/sign-up";
            }
    
            //ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต (๋กœ๊ทธ์ธ ์™„๋ฃŒ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰์…˜)
            return "user/login";
    }

     

    ๐ŸŽˆ ์ฐธ๊ณ 

     

    ๐Ÿ“Œ ์œ„์˜ ๋ฐฉ๋ฒ•1๊ณผ ๋ฐฉ๋ฒ•2์„ ๋ชจ๋‘ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์— ๋Œ€ํ•œ ์ฃผ์˜์‚ฌํ•ญ

    ๋งŒ์•ฝ ๋™์ผํ•œ ํ•„๋“œ์— @NotNull๊ณผ Validator ํด๋ž˜์Šค๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•œ ๋ฐฉ์‹์„ ๋ชจ๋‘ ์ ์šฉํ•˜์˜€๋‹ค๋ฉด, @NotNull๊ณผ ์ง์ ‘ ๊ตฌํ˜„ํ•œ Validator๋ฅผ ๋ชจ๋‘ ๊ฑฐ์น˜๊ฒŒ ๋œ๋‹ค.

    @NotNull์—์„œ ์ฒดํฌ๊ฐ€ ๋˜์—ˆ๋‹ค๊ณ  ๊ตฌํ˜„ํ•œ Validator ํด๋ž˜์Šค์˜ isValid()๋ฅผ ์•ˆ๊ฑฐ์น˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋ฌด์กฐ๊ฑด ๋‘˜๋‹ค ๊ฑฐ์น˜๊ฒŒ ๋œ๋‹ค.

    ์ด๋•Œ ์ ์ ˆํ•œ if๋ฌธ ์กฐ๊ฑด ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด, null ๊ฐ’์ด ๋“ค์–ด์™”์„ ๋•Œ ๋™์ผํ•œ ํ•„๋“œ์— ๋Œ€ํ•ด 2๊ฐœ์˜ error๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค. 

     

    ๋”ฐ๋ผ์„œ ์•„์˜ˆ Validator ํด๋ž˜์Šค์—์„œ๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•˜๊ฒŒ ํ•˜๋“ ์ง€, ํ˜น์€ Validator ํด๋ž˜์Šค ๋‚ด๋ถ€์˜ if๋ฌธ์— ์กฐ๊ฑด์„ ์ž˜ ์ •์˜ํ•ด์ค˜์•ผ ํ•œ๋‹ค. (์ด๊ฒƒ์„ ๊ฐ„๊ณผํ•˜๊ณ  ์˜ค๋ฅ˜๊ฐ€ 2๊ฐœ ๋‚˜์˜จ๋‹ค๋Š” ๊ฒƒ์„ ์ข€ ์ง€๋‚˜์„œ์•ผ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค..^^)

     

    โ–ถ๏ธ ์˜ˆ์‹œ ์ฝ”๋“œ

    //@NotBlank๋ฅผ ์„ค์ •ํ•ด์คฌ์„ ๋•Œ
    public class sampleDto {
    	...
    	@NotBlank
    	private String email;
    	...
    }
    
    //์ „๋‹ฌ๋œ ๊ฐ’์ด blank๋‚˜ null์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ˆ˜ํ–‰
    public class sampleValidator implements ConstraintValidator<...> {
    
    	@Override
    	public boolean isValid(...) {
    		...
    		if (!StringUtils.isBlank(value.getEmail()) && !validationUtils.emailMatch(value.getEmail())) {
                addConstraintViolation(context, "์ด๋ฉ”์ผ ํ˜•์‹์œผ๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", "email");
                invalidCount++;
        }
    	}
    }
    

    ์œ„์™€ ๊ฐ™์€ ์˜ˆ์‹œ ์ƒํ™ฉ์—์„œ ๋งŒ์•ฝ @NotBlank๋กœ ์„ค์ •ํ•ด์คฌ๋‹ค๋ฉด, ์ปค์Šคํ…€ Validator ํด๋ž˜์Šค์—์„œ๋Š” blank๋‚˜ null์ด ์•„๋‹Œ ๊ฒฝ์šฐ์—๋งŒ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ถ”๊ฐ€๋กœ ์ˆ˜ํ–‰ํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

    ๋Œ“๊ธ€

Designed by Tistory.