@Valid 를 이용해 @RequestBody 객체 검증하기[쟈미의 devlog]
Spring Boot의 @Valid 어노테이션이 동작하지 않는 이슈[Chasing Yesterday - Jinhong]
Spring MethodArgumentNotValidException (@Valid 예외처리)[BrantHwang]
@Valid 세팅 및 사용법[Simple is Best!]
@Valid Annotation은 javax.validation에 포함된 Dependency로,
@RequestBody Annotation으로 Mapping되는 Java 객체의 유효성 검증을 수행하는 Annotation이다.
해당 글은 Maven을 통해 pom.xml에 추가하는 방식으로,
Dependency를 추가하는 방법을 설명한다.
pom.xml에 아래 내용을 추가하자.
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>
아래 groupId가 javax.validation인 dependency는 현재 Spring 최신 버전들에서 지원되지 않고 있다.
만약 아래
<groupId>javax.validation</groupId>로 pom.xml에 추가해둔 상태라면,
위 <groupId>org.hibernate.validator</groupId>를 사용하자.
본인은 이것 때문에 꽤 애먹었다.
먼저 간단하게 사용법을 알아보면,
아래 세 가지 과정을 거치면 된다.
유효성 검증을 원하는 객체에 아래와 같이 javax.validation.constraints에 있는 Annotation을 객체 Field에 선언해주자.
관련 Annotation에 대한 설명은 아래에서 설명
import java.util.Date;
import javax.validation.constraints.NotNull;
import project.enums.jwt.JWTRoles;
public class JWTBody {
private String subject;
private String issuer;
@NotNull(message = "UserID는 필수 값입니다.")
private String userId;
private String platformId;
private JWTRoles role;
private Date expireDate;
@RequestBody를 통해 Binding하는 Controller의 method parameter에서,
유효성 검증을 원하는 해당 객체 옆에 @Valid Annotation을 추가하자.
import javax.validation.Valid;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import project.model.auth.JWTBody;
import project.model.common.CommonResponse;
import project.service.auth.AuthService;
@RequestMapping("tokens")
@RestController
public class TokenController {
@SuppressWarnings("unused")
private static final Logger LOG = Logger.getLogger(TokenController.class);
@Autowired
private AuthService authService;
@PostMapping("")
public ResponseEntity<CommonResponse<String>> createToken(@RequestBody @Valid JWTBody jwtBody) {
return ResponseEntity.ok(new CommonResponse<>(authService.createJWT(jwtBody)));
}
}
이제 유효성 검증을 원하는 객체에 선언한 Constraint Annotation에 따라서,
유효성 검증에 실패할 경우 MethodArgumentNotValidException이 발생하는데 이 Exception을 Handling해주자.
본인은 현재 ControllerAdvice를 통해 Handling 하는중이다.
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class ExceptionController {
@SuppressWarnings("unused")
private static final Logger LOG = Logger.getLogger(ExceptionController.class);
@ExceptionHandler(MethodArgumentNotValidException.class)
protected Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
return e.getBindingResult().getAllErrors();
}
}
이제 해당 객체에 유효하지 않은 @RequestBody를 요청하면,

아래와 같이 유효성을 설정한대로 Error 내용을 확인할 수 있다.
좀 더 유연한 유효성 검증을 위해 Validator에서 제공하는,
Constraints Annotation에 대해 설명하면 검증 유형 별로 아래와 같다.
null 이 아닌 값으로, 공백이 아닌 문자를 하나 이상 포함한다.
반드시 값이 존재하고 공백 문자를 제외한 길이가 0보다 커야 한다.
@NotBlank(message = "이름은 필수 값 입니다.")
private String name;
null 이거나 empty(빈 문자열)가 아닌 값.
반드시 값이 존재하고 길이 혹은 크기가 0보다 커야한다.
@NotEmpty(message = "이름은 필수 값 입니다.")
private String name;
null 이 아닌 어떤 타입이든 수용 한다.
반드시 값이 있어야 한다.
@NotNull(message = "이름은 필수 값 입니다.")
private String name;
어떤 타입이든 수용하며 Null이어야 한다.
여기서 문자열에 Null을 허용하지 않는,
@NotNull, @NotEmpty, @NotBlank를 비교해보면 아래와 같다.
| Annotation | null | "" | " " |
|---|---|---|---|
| @NotNull | Invalid | Valid | Valid |
| @NotEmpty | Invalid | Invalid | Valid |
| @NotBlank | Invalid | Invalid | Invalid |
지정된 최대 값보다 작거나 같아야 한다.
Annotation에 필수적으로 int value로 max 값을 지정한다.
@Max(value = 99999)
private int max;
지정된 최소 값보다 크거나 같아야 한다.
Annotation에 필수적으로 int value로 min 값을 지정한다.
@Min(value = 1)
private int min;
지정된 최대 값보다 작거나 같아야 한다.
Annotation에 필수적으로 String value로 max 값을 지정한다.
@DecimalMax(value = "999999")
private BigInteger decimalMax;
지정된 최소 값보다 크거나 같아야 한다.
Annotation에 필수적으로 String value로 min 값을 지정한다.
@DecimalMin(value = "1")
private BigInteger decimalMin;
해당 Annotation들이 적용 가능한 Type은,
BigDecimal, BigInteger, CharSequence, byte, short, int, long과 이에 대응하는 Wrapper Class 들이다.
double, float는 Rounding Error 때문에 지원 X
null도 valid로 간주된다.
여기서 @DecimalMax, @DecimalMin과,
@Max, @Min의 차이는 범위 값의 차이이다.
@.*?max(value = String or Integer)
value property에 String을 사용하느냐,
Integer를 사용하느냐에 따라 범위 값이 현저히 달라지기 때문이다.
양수 값만 가능하다.
@Positive
private int positive;
양수 거나 0인 값만 가능하다.
@PositiveOrZero
private int positiveOrZero;
음수 값만 가능하다.
@Negative
private int negative;
음수 거나 0인 값만 가능하다.
@NegativeOrZero
private int negativeOrZero;
해당 Annotation들이 적용 가능한 Type은,
BigDecimal, BigInteger, CharSequence, byte, short, int, long, double, float과 이에 대응하는 Wrapper Class 들이다.
null도 valid로 간주된다.
현재보다 미래의 날짜, 시간만 가능하다.
@Future
private Date future;
현재거나 미래의 날짜, 시간만 가능하다.
@FutureOrPresent
private Date futureOrPresent;
현재보다 과거의 날짜, 시간만 가능하다.
@Past
private Date past;
현재거나 과거의 날짜, 시간만 가능하다.
@PastOrPresent
private Date pastOrPresent;
해당 Annotation들이 적용 가능한 Type은 아래와 같다.
null도 valid로 간주된다.
현재의 기준은 ClockProvider의 가상 머신에 따라 정의하며,
필요한 경우 default time zone을 적용한다.
@가 포함된 올바른 이메일 양식만 가능하다.
@Email
private String email;
허용 범위 내의 숫자 값만 가능하다.
@Digits는 int integer property로 허용되는 최대 정수 자릿 수를 설정하고,
int fraction property로 허용되는 최대 소수 자릿 수를 설정한다.
@Digits(integer = 3, fraction = 4)
private Integer digits;
해당 Annotation이 적용가능한 Type은,
BigDecimal, BigInteger, CharSequence, byte, short, int, long과 이에 대응하는 Wrapper Class이다.
null도 valid로 간주된다.
항상 True인 값이어야 한다.
@AssertTrue
private boolean true;
항상 False인 값이어야 한다.
@AssertFalse
private boolean false;
해당 Annotation이 적용가능한 Type은,
Boolean, boolean 이다.
max, min property로 설정한 경계(포함)에 있는 크기의 값이어야 한다.
int max property보다 크기가 작거나 같아야 하고,
int min property보다 크거나 같아야 한다.
해당 property 들은 필수
@Size(max = 3, min = 1)
private String size;
해당 Annotation이 적용가능한 Type은,
CharSequence (character sequence의 length),
Collection (collection 의 size),
Map (map 의 size),
Array (array 의 length) 이다.
null도 valid로 간주된다.
지정한 정규식에 대응되는 문자열 값만 가능하다.
String regexp는 필수 property로 정규식 문자열을 지정한다.
Java Pattern Pacakge Convension을 따름
@Pattern(regexp = "^.*?([0-9]+)[\?|!]$")
private String pattern;
해당 Annotation이 적용가능한 Type은,
CharSequence이다.
null도 valid로 간주된다.