-
[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"; }
๐ ์ฐธ๊ณ
- https://tecoble.techcourse.co.kr/post/2021-06-21-custom-annotation/
- https://cheese10yun.github.io/ConstraintValidator/
๐ ์์ ๋ฐฉ๋ฒ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์ด ์๋ ๊ฒฝ์ฐ์๋ง ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ์ถ๊ฐ๋ก ์ํํ๋๋ก ํ๋ ๊ฒ์ด๋ค.
'Back-end > TIL' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ