Java에서는 null 값에 대해서 접근 하려고 할 때 null pointer exception이 발생 함으로, 이러한 부분을 방지 하지 위해서 미리 검증을 하는 과정
- 검증해야 할 값이 많은 경우 코드의 길이가 길어 진다
- 구현에 따라서 달라 질 수 있지만 Service Logic과의 분리가 필요
- 흩어져 있는 경우 어디에서 검증을 하는지 알기 어려우며, 재사용의 한계가 있음
- 구현에 따라 달라 질 수 있지만, 검증 Logic이 변경 되는 경우 테스트 코드 등 참조하는 클래스에서 Logic이 변경되어야 하는 부분이 발생 할 수 있음
어노테이션 | 의미 | 기타 |
@Size | 문자 길이 측정 | Int Type 불가 |
@NotNull | null 불가 | |
@NotEmpty | null, “” 불가 | |
@NotBlank | null, “”,” “불가 | |
@Past | 과거 날짜 | |
@PastOrPresent | 오늘이거나 과거 날짜 | |
@Future | 미래 날짜 | |
@FutureOrPresent | 오늘이거나 미래 날짜 | |
@Pattern | 정규식 적용 | |
@Max | 최대값 | |
@Min | 최소값 | |
@AssertTrue / False | 별도 Logic 적용 | |
@Valid | 해당 object valication 실행 |
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation' // 추가
}
Gradle 디펜던시에 validation 추가
package com.example.validation.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
public class User {
@NotBlank
private String name;
@Min(value = 0)
private int age;
@Email
private String email;
@Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "핸드폰 번호의 양식과 맞지 않습니다. 01x-xxx(x)-xxxx")
private String phoneNumber;
/* 이하 생략 */
}
@Valid 을 사용한 객체는 안에 Validation과 관련된 어노테이션이 붙은 멤버 변수(속성) 값을 검증한다.
각 어노테이션은 message 값 설정을 통해 에러 발생 시 원하는 메세지로 수정 가능하다.
package com.example.validation.controller;
import com.example.validation.dto.User;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class ApiController {
@PostMapping("/user")
public ResponseEntity user(@Valid @RequestBody User user, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
StringBuilder sb = new StringBuilder();
bindingResult.getAllErrors().forEach(objectError -> {
FieldError field = (FieldError) objectError;
String message = field.getDefaultMessage();
System.out.println("field : " + field.getField() + ", message : " + message);
sb.append("field : ").append(field.getField()).append(", message : ").append(message);
});
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(sb.toString());
}
// logic
return ResponseEntity.ok(user);
}
}
컨트롤러 단에서 검증 시에는 BindingResult 객체를 활용하여 에러 발생 및 메시지 확인이 가능하다.
Spring Boot Custom Validation
1. AssertTrue / False 와 같은 method 지정을 통해서 Custom Logic 적용 가능
메소드명을 is*로 시작해야 한다.
package com.example.validation.dto;
import jakarta.validation.constraints.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class User {
@NotBlank
private String name;
@Min(value = 0)
private int age;
@Email
private String email;
@Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "핸드폰 번호의 양식과 맞지 않습니다. 01x-xxx(x)-xxxx")
private String phoneNumber;
@Size(min = 6, max = 6)
private String reqYearMonth; // yyyyMM
/* 중간 생략 */
public String getReqYearMonth() {
return reqYearMonth;
}
public void setReqYearMonth(String reqYearMonth) {
this.reqYearMonth = reqYearMonth;
}
@AssertTrue(message = "yyyyMM의 형식에 맞지 않습니다.")
public boolean isReqYearMonthValidation() {
try {
LocalDate localDate = LocalDate.parse(getReqYearMonth()+"01", DateTimeFormatter.ofPattern("yyyyMMdd"));
} catch (Exception e) {
return false;
}
return true;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
", reqYearMonth='" + reqYearMonth + '\'' +
'}';
}
}
2. ConstraintValidator 를 적용하여 재사용이 가능한 Custom Logic 적용 가능
- message: 유효성 검사 실패 시 알릴 메시지 설정
- groups: 유효성 검사를 상황별로 분리하여 적용할 때 사용.
-payload: 검증 실패 시 추가 정보를 전달하거나 메타데이터를 활용할 때 사용.
-pattern (커스텀): YearMonthValidator에서 검증 시 사용할 패턴 설정
package com.example.validation.annotation;
import com.example.validation.validator.YearMonthValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = {YearMonthValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface YearMonth {
String message() default "yyyyMM 형식에 맞지 않습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String pattern() default "yyyyMMdd";
}
@Constraint 어떤 Custom Validator 클래스로 검증을 할 것 인지 설정한다. Custom Validator 클래스는 ConstraintValidator 인터페이스를 구현한 클래스로 정의할 수 있다.
ConstraintValidator<어노테이션명, 타입명>
package com.example.validation.validator;
import com.example.validation.annotation.YearMonth;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class YearMonthValidator implements ConstraintValidator<YearMonth, String> {
private String pattern;
@Override
public void initialize(YearMonth constraintAnnotation) {
this.pattern = constraintAnnotation.pattern();
}
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
try {
LocalDate localDate = LocalDate.parse(value+"01", DateTimeFormatter.ofPattern(this.pattern));
} catch (Exception e) {
return false;
}
return true;
}
}
initialize에서 커스텀 어노테이션 사용 시 설정한 값을 가져온다. 그 후에 isValid 메소드에서 검증 로직을 추가한다.
package com.example.validation.dto;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
public class User {
@NotBlank
private String name;
@Max(value = 90)
private int age;
@Valid
private List<Car> cars;
/* 생략 */
}
Validation를 적용한 객체 안에 또 다른 객체가 있을 경우에 또 다른 객체 또한 Validator이 필요한 경우에는 반드시 @Valid 어노테이션을 추가해야한다.
'Spring' 카테고리의 다른 글
Filter (1) | 2025.04.15 |
---|---|
Spring Boot Exception 처리 (0) | 2025.03.24 |
Java Bean 규약 (0) | 2025.03.24 |
AOP 관점지향 프로그램 (0) | 2025.03.23 |
IoC와 DI (0) | 2025.03.19 |