@Valid란 빈 검증기(Bean Validator)를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션이다.
다음의 의존성을 build.gradle에 추가해줘야 한다!
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation'
예시
@Getter
@Setter
public class MemberDTO {
@NotBlank
@JsonProperty("user_id")
private String userId;
@NotBlank
@Pattern(regexp="(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,20}",
message = "비밀번호는 영문 대,소문자와 숫자, 특수기호가 적어도 1개 이상씩 포함된 8자 ~ 20자의 비밀번호여야 합니다.")
private String pw;
@NotBlank
private String name;
@Email
private String email;
@NotBlank
private String contact;
public Member convertToEntity(){
return new Member(userId,pw,name,email,contact);
}
}
그리고 다음과 같이 컨트롤러의 메소드에 @Valid를 붙여주면 유효성 검증이 진행된다.
@PostMapping("/join/api/result") // After
public ApiUtils.ApiResult<String> joinByApiResult(@Valid @RequestBody MemberDTO memberDTO,
Errors errors) {
...
}
@NotNull: 해당 값이 null이 아닌지 검증함
@NotEmpty: 해당 값이 null이 아니고, 빈 스트링("") 아닌지 검증함(" "은 허용됨)
@NotBlank: 해당 값이 null이 아니고, 공백(""과 " " 모두 포함)이 아닌지 검증함
@AssertTrue: 해당 값이 true인지 검증함
@Size: 해당 값이 주어진 값 사이에 해당하는지 검증함(String, Collection, Map, Array에도 적용 가능)
@Min: 해당 값이 주어진 값보다 작지 않은지 검증함
@Max: 해당 값이 주어진 값보다 크지 않은지 검증함
@Pattern: 해당 값이 주어진 패턴과 일치하는지 검증함
입력 파라미터의 유효성 검증은 컨트롤러에서 최대한 처리하고 넘겨주는 것이 좋다. 하지만 개발을 하다보면 불가피하게 다른 곳에서 파라미터를 검증해야 할 수 있다. Spring에서는 이를 위해 AOP 기반으로 메소드의 요청을 가로채서 유효성 검증을 진행해주는 @Validated를 제공하고 있다.
클래스에 @Validated를 붙여주고, 유효성을 검증할 메소드의 파라미터에 @Valid를 붙여주면 유효성 검증이 진행된다.
@Service
@Validated
public class MemberService {
public void addMember(@Valid AddMemberRequest addMemberRequest) {
...
}
}
@Valid에 의해 발생한 Validation Error는 기본적으로 Errors 인터페이스 타입의 객체에 담긴다.
따라서 메서드의 인자로 Errors 타입의 객체를 받는지 or 안받는지에 따라 처리가 달라진다.
상황1) Errors 타입 객체를 포함하지 않았을 때 (400 Bad Request 자동 반환)
@PostMapping("/user/sign-up")
public ResponseEntity<SingleResult<UserDto.UserSignUpResDto>> userSignUp(
@RequestBody @Valid UserDto.UserSignUpReqDto userSignUpReqDto) {
...
}
상황2) Errors 타입 객체를 포함했을 때 (if문으로 추가 처리해줘야 함)
@PostMapping("/user/sign-up")
public ResponseEntity<SingleResult<UserDto.UserSignUpResDto>> userSignUp(
@RequestBody @Valid UserDto.UserSignUpReqDto userSignUpReqDto, Errors errors) {
...
if (errors.hasErrors()) { ... }
...
}
나는 Errors 타입 객체를 포함해서 if문으로 추가 처리를 해줬다.
@PostMapping("/join/api/result") // After
public ApiUtils.ApiResult<String> joinByApiResult(@Valid @RequestBody MemberDTO memberDTO,
Errors errors) {
if (errors.hasErrors()) {
return error("유효성 에러!",HttpStatus.BAD_REQUEST);
}
...
}