๋ฐ์ํ
Intro
- ์ฌ์ฉ์ ํน์ ํ๋ก ํธ์๋์์ ์์ฒญํ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ๋ ๊ฒ์ ์ค์
- Spring Boot์ ๊ฐ์ ํ๋ ์์ํฌ์์๋ @Valid, @Validated, validation ๊ด๋ จ ์ ๋ํ ์ด์ ์ ์ฌ์ฉํด์ ์ ํจ์ฑ ๊ฒ์ฆ์ด ๊ฐ๋ฅํจ
- ๋ณต์กํ ๋น์ฆ๋์ค ๋ก์ง์ด๋ ํน์ ์๊ตฌ์ฌํญ์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ ๊ฒ์ฆ ์ ๋ํ ์ด์ ์ ์ฌ์ฉํ๊ธฐ ์ด๋ ค์ธ ์ ์์
- Spring Boot์์๋ ์ฌ์ฉ์ ์ ์์ validation์ ์์ฑํ ์ ์์
์์ ์ฝ๋
@Getter
@ToString
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class RegistUserRequest {
@Pattern(regexp = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}$", message = "์ ํจํ ์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค.")
private String email;
@Pattern(regexp = "^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$", message = "์ ํจํ ํด๋ํฐ ๋ฒํธ๊ฐ ์๋๋๋ค.")
private String phoneNumber;
@Min(value = 19, message = "19์ธ ์ด์ ๊ฐ์
๊ฐ๋ฅํฉ๋๋ค.")
private int age;
@NotBlank
private String name;
@Pattern(regexp = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}$", message = "์ ํจํ ์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค.")
private String recommendedEmail;
}
- ์์ ๊ฐ์ ์์ฒญ ๋ฐ์ดํฐ๋ฅผ ํ์๋ก ํ ๋ ๋ง์ฝ recommendedEmail์ด ์ ํ ๊ฐ๋ฅํ ์ต์ ๊ฐ์ด๋ผ๊ณ ํ์
{
"email" : "test@naver.com",
"phone_number" : "010-2034-1122",
"age" : 19,
"name" : "test",
"recommended_email" : ""
}
- ์์ฒญ ๋ฐ์ดํฐ์ recommended_email์ ๋ณด๋ด์ง ์๋๋ค๋ฉด validation ์ ๋ํ ์ด์ ์ ๋ฐ์ดํฐ์ ์ ํจ์ฑ ๊ฒ์ฆ์ ํ์ง ์์ง๋ง, ํ๋์ ๊ฐ์ ๋ณด๋ด๋ ๊ฒฝ์ฐ ์ ํจ์ฑ ๊ฒ์ฆ์ ์ค์ํ๋ค
{
"code": 40001,
"msg": "๋ฐ์ดํฐ ๊ฒ์ฆ์ ์คํจํ์ต๋๋ค.",
"field_errors": [
{
"field": "recommendedEmail",
"rejectedValue": "",
"reason": "์ ํจํ ์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค."
}
]
}
- validation์ ์คํจํ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์
- ์ต์ ์ธ ๊ฒฝ์ฐ ๊ฐ์ด ์์ ๋(๊ฐ์ด ์๋๊ฑด null, "", " "๋ก ์ ์)๋ง ๊ฒ์ฆํ๊ณ ์ถ์๋ ๊ธฐ์กด validation์ ์ฌ์ฉํ ์ ์์
- ๋ํ email, recommendedEmail๊ณผ ๊ฐ์ด ์ด๋ฉ์ผ์ ๋ค๋ฅธ ์์ฒญ์๋ ๋ฐ๋ณต์ ์ผ๋ก ์ฌ์ฉ๋ ์ ์๋๋ฐ ์ด๋ ์ด๋ฉ์ผ ๊ธธ์ด ์ ํ์ ๋ํ ์๊ตฌ์ฌํญ์ด ๋ณ๊ฒฝ๋๋ ๊ฒฝ์ฐ ์ผ์ผ์ด ๋ชจ๋ ํ๋๋ฅผ ํ์ธํ๋ฉฐ ์ ๊ท์์ ์์ ํด ์ค์ผํจ
- ๊ธฐ์กด validation์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด custom validation annotation์ ๋ง๋ค์ด ์ฌ์ฉํ ์ ์๋ค
(validation ์ํ ํ ๋ฐ์ํ๋ exception์ ๊ณตํต response๋ก ์ ์ํ๋ ๋ฐฉ๋ฒ์ ์ด์ ๊ธ ์ฐธ๊ณ : 2024.11.09 - [BE/๐ Spring] - [Spring Boot] RestControllerAdvice๋ก Validation Exception ํธ๋ค๋งํ๊ธฐ)
๊ธฐ๋ํจ๊ณผ
- ๊ฐ๋ ์ฑ ํฅ์
- ์ค๋ณต๋๋ ์ฝ๋ ์ต์ํ
- ๊ธฐ์กด validtion์์ ๊ตฌํํ๊ธฐ ํ๋ ๊ธฐ๋ฅ ๊ตฌํ
Custom annotation
@Constraint(validatedBy = RestrictionValidator.class)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Restriction {
boolean required() default false;
int min() default Integer.MIN_VALUE;
int max() default Integer.MAX_VALUE;
PatternType patternType() default PatternType.NONE;
String message() default "์ ํจํ์ง ์์ ๊ฐ์
๋๋ค.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
enum PatternType {
NONE,
PHONE,
EMAIL
}
}
- ์ ๋ํ ์ด์ ์์ ์ฌ์ฉํ ์ต์ required, min, max, patternType, message๋ฅผ ์ถ๊ฐ
- ์์ฃผ ์ฌ์ฉ๋๋ ํจํด์ PatternType์ผ๋ก ์ ์
ConstraintValidator
public class RestrictionValidator implements ConstraintValidator<Restriction, Object> {
private boolean required;
private int min;
private int max;
private Restriction.PatternType patternType;
private static final Pattern PHONE_PATTERN = Pattern.compile("^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$");
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}$");
@Override
public void initialize(Restriction constraintAnnotation) {
this.required = constraintAnnotation.required();
this.min = constraintAnnotation.min();
this.max = constraintAnnotation.max();
this.patternType = constraintAnnotation.patternType();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (required && value == null) {
return false;
}
if (value instanceof String stringValue) {
if (required && stringValue.trim().isEmpty()) {
return false;
}
if (!required && stringValue.trim().isEmpty()) {
return true;
}
if (stringValue.length() < min || stringValue.length() > max) {
return false;
}
if (patternType == Restriction.PatternType.PHONE && !PHONE_PATTERN.matcher(stringValue).matches()) {
return false;
}
if (patternType == Restriction.PatternType.EMAIL && !EMAIL_PATTERN.matcher(stringValue).matches()) {
return false;
}
} else if (value instanceof Integer intValue) {
if (intValue < min || intValue > max) {
return false;
}
}
return true;
}
}
- isValid์์ false๋ฅผ ๋ฐํํ๋ฉด MethodArgumentNotValidException์ด ๋ฐ์ํจ
- ConstraintValidator<Restirction, Object>๋ ๊ฐ๊ฐ custom annotation, ๊ฒ์ฆํ ๊ฐ์ฒด์ ํ์
- ๊ฒ์ฆํ ๊ฐ์ฒด๋ int, String ๋ฑ ๋ค์ํ๋ฏ๋ก Object๋ก ์ง์
์ ์ฉ
@Getter
@ToString
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class RegistUserRequest {
@Restriction(required = true, patternType = Restriction.PatternType.EMAIL, message = "์ ํจํ ์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค.")
private String email;
@Restriction(required = true, patternType = Restriction.PatternType.PHONE, message = "์ ํจํ ํด๋ํฐ ๋ฒํธ ํ์์ด ์๋๋๋ค.")
private String phoneNumber;
@Restriction(required = true, min = 19, message = "19์ธ ์ด์ ๋ถํฐ ๊ฐ์
๊ฐ๋ฅํฉ๋๋ค.")
private int age;
@Restriction(required = true, message = "์ด๋ฆ์ ํ์ ๊ฐ์
๋๋ค.")
private String name;
@Restriction(required = false, patternType = Restriction.PatternType.EMAIL, message = "์ ํจํ ์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค.")
private String recommendedEmail;
}
- ๊ธฐ์กด Validation ๋ก์ง๋ค์ ์ ๊ฑฐํ๊ณ Restriction ์ถ๊ฐ
ํ ์คํธ
{
"email" : "test@naver.com",
"phone_number" : "010-2034-1122",
"age" : 12,
"name" : "test",
"recommended_email" : ""
}
// @Restriction(required = true, min = 19, message = "19์ธ ์ด์ ๋ถํฐ ๊ฐ์
๊ฐ๋ฅํฉ๋๋ค.")
// private int age;
{
"code": 40001,
"msg": "๋ฐ์ดํฐ ๊ฒ์ฆ์ ์คํจํ์ต๋๋ค.",
"field_errors": [
{
"field": "age",
"rejectedValue": "12",
"reason": "19์ธ ์ด์ ๋ถํฐ ๊ฐ์
๊ฐ๋ฅํฉ๋๋ค."
}
]
}
- request์์ ์ ํจํ์ง ์์ age๋ฅผ ๋ณด๋ด๋ฉด, ๊ฒ์ฆ์ ์คํจํ๋ค๋ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์
@Restriction(required = false, patternType = Restriction.PatternType.EMAIL, message = "์ ํจํ ์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค.")
private String recommendedEmail;
- recommended_email์ ๊ฒฝ์ฐ ์ต์ ๊ฐ์ด๊ธฐ ๋๋ฌธ์ ๊ฐ์ด ์๋ ๊ฒฝ์ฐ์๋ง ์ด๋ฉ์ผ ํจํด ๊ฒ์ฆ
// ํ
์คํธ 1
{
"email" : "test@naver.com",
"phone_number" : "010-2034-1122",
"age" : 19,
"name" : "test",
"recommended_email" : ""
}
// ํ
์คํธ 2
{
"email" : "test@naver.com",
"phone_number" : "010-2034-1122",
"age" : 19,
"name" : "test",
"recommended_email" : "test"
}
- ์์ ๊ฐ์ด request๋ฅผ ๋ณด๋ธ ๊ฒฝ์ฐ
// test1
RegistUserRequest(email=test@naver.com, phoneNumber=010-2034-1122, age=19, name=test, recommendedEmail=)
// test2
{
"code": 40001,
"msg": "๋ฐ์ดํฐ ๊ฒ์ฆ์ ์คํจํ์ต๋๋ค.",
"field_errors": [
{
"field": "recommendedEmail",
"rejectedValue": "test",
"reason": "์ ํจํ ์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค."
}
]
}
- test1์์๋ recommendedEmail์ ๊ฐ์ด ์์ด ๊ฒ์ฆ์ ํ์ง ์์๊ณ , ์๋ํ response๊ฐ์ ๋ฆฌํด
- test2์์๋ recommendedEmail์ ๊ฐ์ด ์์ผ๋ฏ๋ก ์ด๋ฉ์ผ ํจํด์ ๊ฒ์ฆํ๊ณ ์๋ฌ ๋ฉ์ธ์ง๋ฅผ request๋ฅผ ์์ฒญํ ํ๋ก ํธ์๊ฒ ์ ๋ฌ
728x90
๋ฐ์ํ
'BE > ๐ Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring Boot] AOP ๊ฐ๋ ์ ๋ฆฌ (0) | 2024.11.17 |
---|---|
[MQTT] Spring Boot ์์ MQTT(mosquitto) ์ฌ์ฉํ๊ธฐ (4) | 2024.11.13 |
[Spring Boot] RestControllerAdvice๋ก Validation Exception ํธ๋ค๋งํ๊ธฐ (1) | 2024.11.09 |
[Spring Boot] Validation์ ๋ฐ์ํ ์ ์๋ ์๋ฌ ์์๋ณด๊ธฐ (0) | 2024.11.08 |
[Validation] spring boot validation ์ฌ์ฉ๋ฒ๊ณผ ์ข ๋ฅ (0) | 2024.11.07 |
๋๊ธ