스프링에서 데이터를 검증하는 방법

윤용운·2022년 4월 23일
post-thumbnail

서론

요청 데이터를 검증하는 방법에 대해 찾아보다가 자바 표준 검증 어노테이션인 @Valid, 그리고 스프링 표준 검증 어노테이션인 @Validated에 대해 알게 되었다. 이것들이 무엇인지, 그리고 어떻게 사용하는지에 대해 알아보았다.

@Valid

요청 데이터를 일일히 검증하는 코드를 작성하는 것은 매우 번거롭고, 귀찮은 일이라고 생각하였다. 일일히 DTO에 매핑할 떄, 모든 파라미터들을 하나하나 검증하고 예외를 발생시키는 코드를 작성한다? 말도 안되는 일이라고 생각했다. 물론 이런 문제들을 해결하는 방법 또한 존재하였다.

Bean Validation

Bean Validation은 유효성을 검증하는 기술 표준이다. 현재 Bean Validation 2.0(JSR-380)까지 명세가 나와 있으며, jakarta.validation에 있는 javax.validation에 인터페이스가 있으며, 이를 구현한 것이 org.hibernate.valitdator이다.

javax.validation 으로 시작하면 특정 구현에 관계없이 제공되는 표준 인터페이스이고,
org.hibernate.validator 로 시작하면 하이버네이트 validator 구현체를 사용할 때만 제공되는 검증 기능이라고 한다. 실무에서 보통 hibernate validator를 사용하고, spring에서도 hibernamte validator를 사용하므로 편하게 사용하면 된다고 한다.

동작 원리

디스패처 서블릿 동작 중, 컨트롤러 메소드를 호출하는 과정에서 메소드의 값을 처리해주는 ArgumentResolver가 동작하는데, 이때 @Valid 역시 ArgumentResolver에 의해 처리된다고 한다. 이러한 이유로, @Valid 어노테이션은 컨트롤러에서만 동작하며, 다른 계층에서 파라미터를 검증하기 위해서는 @Validated와 결합되어야 한다고 한다. (Dispatcher ServletArgumentResolver등에 대해서는 추후 포스팅하도록 하겠다)

사용방법

spring boot 2.3버전 부터는 validation 의존성을 따로 추가해줘야 한다.

2.3 버전 이전에는 spring-boot-starter-web 의존성 내부에 함께 있었지만, 2.3 버전부터 spring-boot-starter-validation으로 분리되었다고 한다. 주의하자.

유효성 검사에 사용할 수 있는 어노테이션은 다음과 같다.

// 필드 위에 작성한다.
@Null  // null만 혀용합니다.
@NotNull  // null을 허용하지 않습니다. "", " "는 허용합니다.
@NotEmpty  // null, ""을 허용하지 않습니다. " "는 허용합니다.
@NotBlank  // null, "", " " 모두 허용하지 않습니다.

@Email  // 이메일 형식을 검사합니다. 다만 ""의 경우를 통과 시킵니다. @Email 보다 아래 나올 @Patten을 통한 정규식 검사를 더 많이 사용합니다.
@Pattern(regexp = )  // 정규식을 검사할 때 사용됩니다.
@Size(min=, max=)  // 길이를 제한할 때 사용됩니다.

@Max(value = )  // value 이하의 값을 받을 때 사용됩니다.
@Min(value = )  // value 이상의 값을 받을 때 사용됩니다.

@Positive  // 값을 양수로 제한합니다.
@PositiveOrZero  // 값을 양수와 0만 가능하도록 제한합니다.

@Negative  // 값을 음수로 제한합니다.
@NegativeOrZero  // 값을 음수와 0만 가능하도록 제한합니다.

@Future  // 현재보다 미래
@Past  // 현재보다 과거

@AssertFalse  // false 여부, null은 체크하지 않습니다.
@AssertTrue  // true 여부, null은 체크하지 않습니다.

// 출처 : https://wildeveloperetrain.tistory.com/25

더욱 자세한 설명은 여기에서 볼 수 있을것이다.

그리고, 컨트롤러에서 @Valid 어노테이션을 붙여주면 유효성 검증이 진행된다.

@PostMapping("/signup")
public ResponseEntity signUp(@Valid @RequestBody UserDto user) {
    ...
}

@Validated

@Validated은 스프링에서 제공하는 인터페이스로, 사용하는 이유는 크게 2가지라고 생각한다. 하나는 다른 계층에서 유효성을 검증하는 것, 그리고 다른 하나는 유효성 검증 그룹의 지정이다. 하나하나 살펴보도록 하겠다.

다른 계층에서 유효성을 검증하는 방법

위에서 @Valid 어노테이션은 작동원리 상 컨트롤러에서만 사용할 수 있다고 하였는데, 불가피한 이유로 다른 레이어에서 파라미터를 검증해야 될 때가 생길 수도 있다.

@Service
@Validated
public class UserService {
	public void signup(@Valid UserDto userDto) {
    	...
    }
}

유효성 검증 그룹의 지정

객체를 검증하는 방법이 달라질 수도 있다. 예를들어, UserDto로 오는 요청들을 다른 방식으로 검증해야 될 경우가 생길 수도 있다. 이를 해결하기 위한 Grouping 기능을 사용할 수도 있다.

// 그룹을 지정하기 위한 마커 인터페이스들
public interface SignUpMarker {}
public interface SignInMarker {}
// Dto에서 그룹 지정
public class UserDto {

    @NotBlank(groups = {SignUpMarker.class, SignInMarker.class})	//회원가입, 로그인 시
    private String accountId;

    @NotBlank(groups = {SignUpMarker.class, SignInMarker.class})	//회원가입, 로그인 시
    private String password;

    @NotBlank(groups = SignUpMarker.class)	// 회원가입만 검증
    private String nickname;
    
    @NotBlank	// 없으면 기본값(Default)
    private String email
}
// Controller에서 검증할 클래스 지정
@PostMapping("/signup")
public ResponseEntity signUp(@Validated(SignUpMarker.class) @RequestBody UserDto user) {
    ...
}

@PostMapping("/signin")
public ResponseEntity signIn(@Validated(SignInMarker.class) @RequestBody UserDto user) {
    ...
}

동작 원리

@Valid와 달리 @Validated는 스프링의 AOP 기반으로 메소드 요청을 인터셉터하여 처리한다고 한다.
@Validated가 클래스 레벨에 선언되면(Controller에서는 생략 가능) 해당 클래스에 유효성 검증을 위한 인터셉터인 MethodValidationInterceptor가 등록되고, 해당 클래스의 메소드들이 호출 될 때 요청을 가로채 인터셉터를 통해 유효성 검증을 한다고 한다.

예외 처리

  • @Valid
    MethodArgumentNotValidException이 발생한다.
  • @Validated
    ConstraintViolationException이 발생한다.

Reference

0개의 댓글