들어가는 말
2부는 @validated 에 예제가 있으면 @Valid 예제가 보고싶다면 아래 링크로 이동해주세요
하지만 큰제목 1~ 2에 대한 내용은 1부와 동일합니다.
2022.09.01 - [JAVA/스프링(Spring)] - [Spring] @Valid, @Validated 에 대해서 정리[1] - @Valid 예제
1. Gradle 및 maven
메이븐저장소로 이동해 원하는 버전의 Hibernate Validator Engine 라이브러리를 다운 받으시면 됩니다.
// validator
implementation group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.2.0.Final'JAVA
2. @Valid와 @Validated 대해서
@Valid
JSR-303 표준 스펙으로써 빈 검증기(Bean Validator)를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션이며, RequestResponseBodyMethodProcessor를 통해 validation을 진행하고 검증에 오류가 있으면MethodArgumentNotValidException이 발생합니다.
스프링 ExceptionResolver의 DefaultHandlerExceptionResolver덕분에 400 에러를 발생시킵니다.
@Validated
@Validated는 AOP 기반으로 메소드 요청을 인터셉터하여 처리된다. @Validated를 클래스 레벨에 선언하면 해당 클래스에 유효성 검증을 위해 인터셉터(MethodValidationInterceptor)가 받아서 처리합니다. 그리고 해당 클래스의 메소드들이 호출될 때 AOP의 포인트 컷으로써 요청을 가로채기 때문에 스프링 빈이라면 유효성 검증을 진행할 수 있습니다.
3. 제약 조건 어노테이션
Anotation | 제약 조건 |
@NotNull | 모든 데이터 타입에 대해 null을 허용하지 않는다. |
@NotEmpty | null과 ""를 허용하지 않는다. (타입 - String, Collection. Map, Array) |
@NotBlank | null과 "", " "(빈 공백 문자열)을 허용하지 않는다. |
@Null | Null만 입력 가능 |
@Size(min=,max=) | 문자열, 배열등의 크기 검증 |
@Pattern(regex=) | 정규식 검증 |
@Max(숫자) | 최대값 검증 |
@Min(숫자) | 최소값 검증 |
@Future | 현재 보다 미래인지 검증 |
@Past | 현재 보다 과거인지 검증 |
@Positive | 양수만 가능 |
@PositiveOrZero | 양수와 0만 가능 |
@Negative | 음수만 가능 |
@NegativeOrZero | 음수와 0만 가능 |
이메일 형식만 가능 | |
@Digits(integer=, fraction = ) | 대상 수가 지정된 정수와 소수 자리 수 보다 작은지 검증 |
@DecimalMax(value=) | 지정된 실수 이하인지 검증 |
@DecimalMin(value=) | 지정된 실수 이상인지 검증 |
@AssertFalse | false 인지 검증 |
@AssertTrue | true 인지 검증 |
@Validated 사용해보기
다른 사용자들의 예제를 보면 @Validated를 클래스에 사용하지만 저는 매개변수에 선언을 하는 예제로 사용합니다.
UserValidGroup
인터페이스안에 그룹화 할 내부 인터페이스 정의해주기
public interface UserValidGroup {
public interface UserCreate{} //유저를 생성할 때 사용할 그룹
public interface UserUpdate{} //유저의 정보를 수정할 때 사용할 그룹
}
ValidTestController
@Validated에 인터페이스로 정의해준 UserValidGroup.UserUpdate.class를 선언해준 것을 볼 수 있습니다. 이유는 바로 다음에 볼 수 있습니다.
@RestController
public class ValidTestController {
/*
* @Validated를 이용해 그룹화하여 검증
* 이메일: 이메일 형식으로 입력을 받아야 한다.
* 이름: 사용자의 이름은 null이면 안된다.
* */
@PostMapping("/userUpdateTest")
public void UserNameUpdate(
@RequestBody
@Validated(UserValidGroup.UserUpdate.class) UserRequestDto userRequestDto) {
System.out.println("로직 수행 중");
System.out.println("변경 완료");
}
}
UserRequestDto
1부에서는 제약 어노테이션만 있었지만 아래코드는 group과 messge를 선언해준 것을 확인할 수 있습니다.
ValidTestController에서 @Validated(UserValidGroup.UserUpdate.class)를 해준이유는 검증할 객체의 제약코드중 그룹이
UserValidGroup.UserUpdate.class로 되어있는 제약만 검증하도록 설정해준 겁니다.
@Getter
@NoArgsConstructor
public class UserRequestDto {
@Email(groups = UserValidGroup.UserUpdate.class, message = "이메일 형식이 아닙니다.")
private String email;
@NotBlank
private String password;
@NotNull(groups = UserValidGroup.UserUpdate.class, message = "name은 null이여서는 안됩니다.")
private String name;
@Min(19)
private int age;
테스트 코드
첫번째와 두번째 코드는 검증에 실패하므로 400 BadRequest가 발생한다.
세번째는 성공코드로 name과 email만 있어도 성공하는지 확인하기 위한 코드이다.
@DisplayName("@Validated 테스트")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@Nested
class Validated {
@DisplayName("이메일 형식 실패")
@Order(1)
@Test
public void 이메일_형식_실패() throws Exception {
UserRequestDto userRequestDto = UserRequestDto.builder()
.email("whitewise")
.age(19)
.name("짱구")
.password("1234")
.build();
String requestBody = mapper.writeValueAsString(userRequestDto);
HttpEntity<String> stringHttpEntity = new HttpEntity<>(requestBody, headers);
//when
ResponseEntity response = restTemplate.postForEntity(
"/userUpdateTest",
stringHttpEntity,
Void.class
);
//then 400 BAD_REQUEST가 확인되어야 정상
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
}
@DisplayName("이름 null 실패")
@Order(2)
@Test
public void 이름_null_실패() throws Exception {
UserRequestDto userRequestDto = UserRequestDto.builder()
.email("whitewise95@gmail.com")
.age(19)
.password("1234")
.build();
String requestBody = mapper.writeValueAsString(userRequestDto);
HttpEntity<String> stringHttpEntity = new HttpEntity<>(requestBody, headers);
//when
ResponseEntity response = restTemplate.postForEntity(
"/userUpdateTest",
stringHttpEntity,
Void.class
);
//then 400 BAD_REQUEST가 확인되어야 정상
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
}
@DisplayName("Validated 성공")
@Order(1)
@Test
public void Validated_성공() throws Exception {
UserRequestDto userRequestDto = UserRequestDto.builder()
.email("whitewise@gmail.com")
.name("짱구")
.build();
String requestBody = mapper.writeValueAsString(userRequestDto);
HttpEntity<String> stringHttpEntity = new HttpEntity<>(requestBody, headers);
//when
ResponseEntity response = restTemplate.postForEntity(
"/userUpdateTest",
stringHttpEntity,
Void.class
);
//then 400 BAD_REQUEST가 확인되어야 정상
assertEquals(HttpStatus.OK, response.getStatusCode());
}
}
테스트 결과
콘솔을 1부와 같게 비슷하게 나온걸 확인 할 수 있습니다. 단지 message만 자신이 정의해준 내용으로 나옵니다.
번외
개발을 하다보면 같은 객체를 사용해야할 때가 많이 있다. 위에 예제는 update할 때 였지만 user를 생성할 때 사용할 경우는 UserCreate 인터페이스를 사용하면 된다. 계속 보자
public interface UserValidGroup {
public interface UserCreate{} //유저를 생성할 때 사용할 그룹
public interface UserUpdate{} //유저의 정보를 수정할 때 사용할 그룹
}
UserRequestDto
UserUpdate뿐만 아니라 group 에 { } 를 감싸서 , 로 구분하고 UserCreate도 사용할 수 있게 되었습니다.
이런 방법이라면 create, delete, update, 더 간다면 Read 뿐만 아니라 여러 API에 하나의 객체로도 많은 다양한 검증을
시도할 수 있습니다.
@Getter
@NoArgsConstructor
public class UserRequestDto {
@Email(groups = { UserValidGroup.UserUpdate.class, UserValidGroup.UserCreate.class }, message = "이메일 형식이 아닙니다.")
private String email;
@NotBlank(groups = UserValidGroup.UserCreate.class, message = "패스워드는 공백만 있으면 안됩니다.")
private String password;
@NotNull(groups = { UserValidGroup.UserUpdate.class, UserValidGroup.UserCreate.class }, message = "name은 null이여서는 안됩니다.")
private String name;
@Min(groups = UserValidGroup.UserCreate.class, value = 19, message = "나이는 19세 이상만 가능합니다.")
private int age;
ValidTestController
UserCreate 메소드를 생성해 UserValidGroup.UserCreate.class 로 group 해준 제약 어노테이션만 검증하도록 @Validated 를 사용해준 것을 볼 수 있습니다.
@RestController
public class ValidTestController {
@PostMapping("/userUpdateTest")
public void UserNameUpdate(
@RequestBody
@Validated(UserValidGroup.UserUpdate.class) UserRequestDto userRequestDto) {
System.out.println("로직 수행 중");
System.out.println("변경 완료");
}
@PostMapping("/userCreatTest")
public void UserCreate(
@RequestBody
@Validated(UserValidGroup.UserCreate.class) UserRequestDto userRequestDto) {
System.out.println("로직 수행 중");
System.out.println("유저 생성");
}
}
전체 코드
1부 - @Valid예제
1부 [Spring] @Valid, @Validated 에 대해서 정리[1] - @Valid예제 - 보러가기
2부 [Spring] @Valid, @Validated 에 대해서 정리[2] - @Validated 예제 - 현재
참고한 자료