@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로 간주된다.