지금까지는 단일조건(예외)를 잡아냈지만 이번에는 복합조건을 잡아내도록 한다
만약 아래 두가지 요소 둘 중 하나만 있어도 된다면?
이러한 조건을 제공하는 어노테이션은 없다.
그렇기 때문에 커스텀한 Validation이 필요하다.
전에 만들었던 UserRegisterRequest.java파일에 private String name 변수가 있을텐데 그 아래에 private String nickName을 추가해준다.
그리고 이름과 별명이 들어있는지 판별하는 메서드를 하나 만들어준다.
작성하면 아래와 같다.
package org.example.validation.model;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.xml.namespace.NamespaceContext;
import java.time.LocalDateTime;
import java.util.Objects;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class UserRegisterRequest {
// @NotNull // !=null
// @NotEmpty // ! = null && name != ""
// @NotBlank // !=null && name != "" && name != " "
private String name;
// @NotBlank
private String nickName;
@Size(min=1,max = 12)
@NotBlank
private String password;
@NotNull //문자가 아니라서 NotBlank나 NotEmpty를 넣어줄 수 없다.
private Integer age;
@Email //이메일 형식 받기
private String email;
@Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$",message = "휴대폰 번호 양식에 맞지 않습니다.") //정규식을 통해 해당 핸드폰번호를 검증함
private String phoneNumber;
@FutureOrPresent
private LocalDateTime registerAt;
//여기에 메서드 추가 됨
@AssertTrue(message = "name or nickname should exist at least one") //리턴값이 트루라면 메시지 설정
public boolean isNameCheck(){
if(Objects.nonNull(name)&&!name.isBlank()){
return true;
}
if(Objects.nonNull(nickName)&&!nickName.isBlank()){
return true;
}
return false;
}
}
이제 닉네임과 이름 둘다 판별한다.
아래의 전화번호를 검사할 때 사용한 정규식 부분을 새로운 어노테이션을 만들어 검사하도록 해보자.
@Pattern(regexp = "^\d{2,3}-\d{3,4}-\d{4}$",message = "휴대폰 번호 양식에 맞지 않습니다.")
private String phoneNumber;
PhoneNumberValidator.java 파일을 하나 만들어주고 그안을 아래와 같이 작성한다.
package org.example.validation.validator;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.example.validation.annotation.PhoneNumber;
import java.util.regex.Pattern;
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber,String> {
private String regxp; // 정규식 패턴을 저장할 변수
@Override
public void initialize(PhoneNumber constraintAnnotation) {
this.regxp= constraintAnnotation.regexp(); // PhoneNumber 어노테이션에서 정의된 정규식 패턴을 가져와 변수에 저장합니다.
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
// validation이 실행될 때 실행되는 메서드입니다. constraintValidatorContext를 사용하여 유효성 검사를 수행합니다.
boolean result = Pattern.matches(regxp, s); // 주어진 문자열 s가 정규식 패턴 regxp와 일치하는지 여부를 판별합니다.
return result; // 결과를 반환합니다.
}
}
이 파일에서는 검증할 규칙을 지정해주고, 결과를 반환한다.
이제 이 규칙을 가지고 어노테이션을 만들어 보자
annotation.java 파일을 만들고 아래와 같이 작성한다.
package org.example.validation.annotation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import org.example.validation.validator.PhoneNumberValidator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Constraint(validatedBy = {PhoneNumberValidator.class}) //어떤 클래스로 검증할 것인지를 알려주는 어노테이션
@Target({ElementType.FIELD})
//이 어노테이션이 적용될 대상을 지정합니다. 여기서는 ElementType.FIELD로 지정되어 있으므로, 이 어노테이션은 필드에만 적용될 수 있습니다. 다른 가능한 값으로는 METHOD, PARAMETER, TYPE, 등이 있습니다
@Retention(RetentionPolicy.RUNTIME)
//이 어노테이션의 유지 정책을 지정합니다. 여기서는 RetentionPolicy.RUNTIME으로 지정되어 있으므로, 이 어노테이션은 런타임 시에도 유지됩니다. 이것은 리플렉션을 통해 어노테이션 정보에 접근할 수 있음을 의미합니다. 다른 유지 정책으로는 SOURCE (소스 코드까지만 유지), CLASS (컴파일된 클래스 파일까지 유지) 등이 있습니다.
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 {};
}
그리고 UserRegisterRequest의 PhoneNumber에 이 어노테이션 @PhoneNumber을 붙여주면 완성이다.