잘못된 형식의 이메일 주소를 입력할 수 있기에 검증해야한다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
...
...
}
build.gradle 파일의 dependencies
항목에 org.springframework.boot:spring-boot-stater-validation
을 추가해야 한다.
✔️ MemberPostDto 유효성 검증 제약 사항
email
(이메일 주소)name
(이름)import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
public class MemberPostDto {
@NotBlank
@Email
private String email;
@NotBlank(message = "이름은 공백이 아니어야 합니다.")
private String name;
@Pattern(regexp = "^010-\\d{3,4}-\\d{4}$",
message = "휴대폰 번호는 010으로 시작하는 11자리 숫자와 '-'로 구성되어야 합니다.")
private String phone;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
MemberPostDto
의 멤버 변수에 적용된 유효성 검증 내용은 다음과 같다.
@NotBlank
@Email
@NotBlank
@NotBlank
의 message 애트리뷰트에 지정한 문자열이 에러 메시지로 콘솔에 출력된다.@Pattern
MemberController에 @Valid 애너테이션을 추가하면 된다.
@RestController
@RequestMapping("/v1/members")
public class MemberController {
@PostMapping
public ResponseEntity postMember(@Valid @RequestBody MemberPostDto memberDto) {
return new ResponseEntity<>(memberDto, HttpStatus.CREATED);
}
...
...
}
이러면 유효성 검사에 걸리게 된다.
✔️ MemberPatchDto 유효성 검증 제약 사항
name
(이름)phone
(휴대폰 번호)import javax.validation.constraints.Pattern;
public class MemberPatchDto {
private long memberId;
// 공백 아닌 문자 1개 이상((공백인 문자 0개 또는 1개)(공백이 아닌 문자 1개 이상)) -> 마지막 맨 바깥 쪽 괄호 조건이 0개 이상(즉, 있어도 되고 없어도 된다)
@Pattern(regexp = "^\\S+(\\s?\\S+)*$", message = "회원 이름은 공백이 아니어야 합니다.")
private String name;
@Pattern(regexp = "^010-\\d{3,4}-\\d{4}$",
message = "휴대폰 번호는 010으로 시작하는 11자리 숫자와 '-'로 구성되어야 합니다.")
private String phone;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public long getMemberId() {
return memberId;
}
public void setMemberId(long memberId) {
this.memberId = memberId;
}
}
MemberPatchDto
클래스의 멤버 변수에 적용된 유효성 검증 내용은 다음과 같다.
memberId
name
phone
MemberPostDto
클래스와 달리 MemberPatchDto
에서는 모두 정규 표현식을 사용했다.
다양한 조건을 선택적으로 검증하고자 할 때 유용한 방법 중 하나가 바로 정규 표현식(Regular Experssion) 이다.
name 멤버 변수에 사용한 “^\S+(\s?\S+)*$” 정규 표현식에서
‘^’은 문자열의 시작을 의미한다.
‘$’는 문자열의 끝을 의미한다.
‘’는 ‘’ 앞에 평가할 대상이 0개 또는 1개 이상인지를 평가한다.
‘\s’는 공백 문자열을 의미한다.
‘\S’ 공백 문자열이 아닌 나머지 문자열을 의미한다.
‘?’는 ‘?’ 앞에 평가할 대상이 0개 또는 1개인지를 의미한다.
‘+’는 ‘+’ 앞에 평가할 대상이 1개인지를 의미한다.
MemberController 클래스에 patchMember() 메서드를 바꾼다.
@RestController
@RequestMapping("/v1/members")
@Validated
public class MemberController {
...
...
@PatchMapping("/{member-id}")
public ResponseEntity patchMember(@PathVariable("member-id") @Min(2) long memberId,
@Valid @RequestBody MemberPatchDto memberPatchDto) {
memberPatchDto.setMemberId(memberId);
// No need Business logic
return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
}
}
이렇게 하면
유효하지 않은 정보를 입력하면 Bad Request를 받을 수 있다.
지금까지 @PathVaribale("member-id") long memberId
에 대한 검증은 하지 않았다.
1 이상의 숫자여야한다 라는 제약을 걸어보자
@RestController
@RequestMapping("/v1/members")
@Validated // (1)
public class MemberController {
...
...
@PatchMapping("/{member-id}")
public ResponseEntity patchMember(@PathVariable("member-id") @Min(1) long memberId,
@Valid @RequestBody MemberPatchDto memberPatchDto) {
memberPatchDto.setMemberId(memberId);
// No need Business logic
return new ResponseEntity<>(memberPatchDto, HttpStatus.OK);
}
}
지금까지 DTO 클래스의 유효성 검증을 위해 사용한 애너테이션은 Jakarta Bean Validation이라는 유효성 검증을 위한 표준 스펙에서 지원하는 내장 애너테이션들이다.
Jakarta Bean Validation 스펙을 구현한 구현체가 바로 Hibernate Validator이다.
Jakarta Bean Validation에 내장된 애너테이션 중에 목적에 맞는 애너테이션이 존재하지 않을 수 있다.
Custom Validator를 구현하기 위한 절차는 다음과 같다.
✔️ Custom Annotation을 정의
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {NotSpaceValidator.class}) // (1)
public @interface NotSpace {
String message() default "공백이 아니어야 합니다"; // (2)
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
NotSpace
애너테이션이 멤버 변수에 추가되었을 때, 동작 할 Custom Validator를 (1)과 같이 추가했다.
(2)는 애너테이션 추가 시 별도로 정의하지 않으면 유효성 검증 실패 시, 표시되는 디폴트 메시지이다.
✔️ Custom Validator 구현
import org.springframework.util.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class NotSpaceValidator implements ConstraintValidator<NotSpace, String> {
@Override
public void initialize(NotSpace constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value == null || StringUtils.hasText(value);
}
}
✔️ 유효성 검증을 위해 Custom Annotation 추가
import javax.validation.constraints.Pattern;
public class MemberPatchDto {
private long memberId;
@NotSpace(message = "회원 이름은 공백이 아니어야 합니다") // (1)
private String name;
@Pattern(regexp = "^010-\\d{3,4}-\\d{4}$",
message = "휴대폰 번호는 010으로 시작하는 11자리 숫자와 '-'로 구성되어야 합니다")
private String phone;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public long getMemberId() {
return memberId;
}
public void setMemberId(long memberId) {
this.memberId = memberId;
}
}
프론트엔드 쪽에서 유효성 검증을 진행했다 하더라도 서버 쪽에서 추가적으로 유효성 검증을 반드시 진행해야 한다.
프론트엔드 쪽에서의 유효성 검증 프로세스는 사용자 편의성 측면에서 필요한 작업니다.
Jakarta Bean Validation의 애터네이션을 이용하면 Controller 로직에서 유효성 검증 로직을 분리할 수 있다.
Jakarta Bean Validation은 애너테이션 기반 유효성 검증을 위한 표준 스펙이다.
Hibernate Validator는 Jakrta Bean Validation 스펙을 구현한 구현체이다.
SPring에서 지원하는 @Validated
애너테이션을 사용하면 쿼리 파라미터 (Query Parameter 또는 Query String) 및 @Pathvariable
에 대한 유효성 검증을 진행할 수 있다.
Jakarta Bean Validation에서 빌트인 (Built-in)으로 지원하지 않는 애너테이션은 Custom Validator를 통해 Custom Annotation을 구현한 후, 적용할 수 있다.