프로젝트를 진행하며 입력값을 받을 때 프론트에서도 검증을 하고 보내주겠지만, 비지니스 로직을 보호하기 위해 서버단에서도 검증을 해주는게 안전하다고 배웠다. 그걸 할 수 있게 해주는게 @Valid라고 한다.
@Valid란
요청 데이터 유효성 검증을 위해 사용하는 어노테이션
DTO에 선언된 제약 조건을 기반으로 입력값을 검증해줌
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class UserRegisterRequest {
@NotBlank
private String username;
private String name;
@Size(min = 1, max = 12)
@NotBlank
private String password;
@NotNull
@Min(1)
@Max(100)
private Integer age;
@Email
private String email;
@PhoneNumber
//@Pattern(regexp = "^\\\\d{2,3}-\\\\d{3,4}-\\\\d{4}$", message = "휴대폰 번호 양식에 맞지 않습니다.")
private String phoneNumber;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
@FutureOrPresent
private LocalDateTime registerAt;
//해당 메서드가 true 여야 검증이 통과
@AssertTrue(message = "name or username은 존재해야 합니다.")
public boolean isNameCheck(){
if(Objects.nonNull(this.name) && !this.name.isBlank()){
return true;
}
if(Objects.nonNull(this.username) && !this.username.isBlank()){
return true;
}
return false;
}
}
해당 코드에서
@Blank, @NotNull, @Pattern 등 필드값에 제약 조건을 건다.
그 후 입력값을 받아오는 Controller에서 검증이 필요한 입력값 앞에 @Valid를 사용한다.
@PostMapping("")
public Api<UserRegisterRequest> register(
@Valid // 입력값 검증 들어감
@RequestBody
Api<UserRegisterRequest> userRegisterRequest
) {
log.info("init : {}", userRegisterRequest);
var body = userRegisterRequest.getData();
Api<UserRegisterRequest> response = Api.<UserRegisterRequest>builder()
.data(body)
.resultCode(String.valueOf(HttpStatus.OK.value()))
.resultMessage(HttpStatus.OK.name())
.build();
return response;
}
자주 사용되는 검증 어노테이션 정리
- @NotNull: 널이 아닌 값
- @NotBlank: NotNull + 빈문자열, 공백문자열 아닌 값(String)
- @NotEmpty: String 이외에도 Collection, Array 와 같이 빈 리스트가 아닌 값
- @Size(min=, max=): 길이 제한
- @Pattern(regexp=): 정규식 패턴 만족
- @Email: 이메일 형식
- @Min/Max(value=): 숫자 타입 값 검증
- @AssertTrue/False: 해당 메서드가 True 혹은 False여야 됨, 검증로직을 만들 수 있음
- @Past/Future: LocalDate의 시간이 현재보다 과거/미래 검증
여기서 이 어노테이션은 검증 트리거일 뿐
@Constraint(validatedBy = PhoneNumberValidator.class) //검증 클래스 추가
@Target({ElementType.FIELD})//어디에 적용할거냐
@Retention(RetentionPolicy.RUNTIME) //언제 실행시킬거냐
public @interface PhoneNumber {
String message() default "핸드 번호 양식에 맞지 않습니다. ex) 000-0000-0000";
String regexp() default "^\\\\d{2,3}-\\\\d{3,4}-\\\\d{4}$";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
이렇게 커스텀으로 만들 수도 있다.
어노테이션을 만들고, 그 어노테이션을 검증할 클래스를 만들면 해당 어노테이션이 붙은 필드를 검사하는 것이다.
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {
private String regexp;
@Override //정규식 패턴 초기화
public void initialize(PhoneNumber constraintAnnotation) {
this.regexp = constraintAnnotation.regexp();
}
@Override //입력받은 값과 정규식이 일치하는지 검사하는 메서드
public boolean isValid(String value, ConstraintValidatorContext context) {
return Pattern.matches(regexp, value);
}
}
다른 어노테이션도 동일하게 동작한다.
마무리
오늘은 필드 검증을 위한 자주 사용하는 어노테이션을 알아봤고, 어떻게 커스텀 어노테이션을 만드는지도 알아봤다.
검증을 철저히해 잘못된 입력을 받아 로직을 돌다가 에러가 나는 걸 막고,
빠르게 검증으로 막을 수 있어 서버에 부담을 줄일 수 있는 좋은 방식인 것 같다.
'공부일지 > 개인학습' 카테고리의 다른 글
| [Spring] 정적 팩토리 메서드(정.팩.메): from() (2) | 2025.08.04 |
|---|---|
| [JAVA] Builder 패턴 이해하기 (0) | 2025.06.20 |
| [리눅스 마스터 2급] 시험 후기 (0) | 2025.06.16 |
| Spring Security 정리 (1) | 2025.06.15 |
| [리눅스 마스터 2급] 2차 모의고사 오답 정리 (0) | 2025.06.12 |
