들어가는 말
@validated @Valid 사용하면 비지니스 로직에
유효성 체크하는 코드가 줄어들어
전체적인 비지니스 로직이 간결해지는 것을 많이 느꼈다.
하지만 항상 쓸 때마다 까먹어 전에 했던 프로젝트를 계속 보는데
이곳 저곳에서 보는것이 매우 불편해,
블로그에 정리하여 한방에 보기위해 글을 정리했습니다.
Gradle 및 maven
메이븐저장소로 이동해 원하는 버전의 Hibernate Validator Engine 라이브러리를 다운 받으시면 됩니다.
// validator
implementation group: 'org.hibernate.validator', name: 'hibernate-validator', version: '6.2.0.Final'
@Valid와 @Validated 대해서
@Valid
JSR-303 표준 스펙으로써 빈 검증기(Bean Validator)를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션이며, RequestResponseBodyMethodProcessor를 통해 validation을 진행하고 검증에 오류가 있으면MethodArgumentNotValidException이 발생합니다.
스프링 ExceptionResolver의 DefaultHandlerExceptionResolver덕분에 400 에러를 발생시킵니다.
@Validated
@Validated는 AOP 기반으로 메소드 요청을 인터셉터하여 처리된다. @Validated를 클래스 레벨에 선언하면 해당 클래스에 유효성 검증을 위해 인터셉터(MethodValidationInterceptor)가 받아서 처리합니다. 그리고 해당 클래스의 메소드들이 호출될 때 AOP의 포인트 컷으로써 요청을 가로채기 때문에 스프링 빈이라면 유효성 검증을 진행할 수 있습니다.
제약 조건 어노테이션
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 인지 검증 |
@Valid 사용해보기
검증할 객체에 제약어노테이션을 사용한다
@Email - 이메일 형식만 받을 수 있다.
@NotBlank - 빈문자열 불가능 ...ex) "", " "
@NotNull - null은 허용이 안된다.
@Min - 최소 19이상의 숫자여야한다.
@Getter
@NoArgsConstructor
public class UserRequestDto {
@Email
private String email;
@NotBlank
private String password;
@NotNull
private String name;
@Min(19)
private int age;
}
컨트롤러에 @Valid 사용하기
@Vaild 어노테이션을 사용해 UserRequestDto에 제약을 검증한다.
@RestController
public class ValidTestController {
@PostMapping("/validTest")
public void validTest(@RequestBody @Valid UserRequestDto userRequestDto) {
System.out.println("로직 수행");
System.out.println(userRequestDto);
}
}
테스트코드 적용
전부 제약조건에 불만족하는 테스트이며 400 BadRequest 에러가 발생한다.
∨ (접은글)테스트코드
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ValidTestControllerTest {
@Autowired
private TestRestTemplate restTemplate;
private ObjectMapper mapper;
private HttpHeaders headers;
@BeforeAll
public void setUp() {
headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
mapper = new ObjectMapper();
}
@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(
"/validTest",
stringHttpEntity,
Void.class
);
//then 400 BAD_REQUEST가 확인되어야 정상
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
}
@DisplayName("나이 19세 미만으로 실패")
@Order(2)
@Test
public void 나이_19세_미만으로_실패() throws Exception {
UserRequestDto userRequestDto = UserRequestDto.builder()
.email("whitewise95@gmail.com")
.age(18)
.name("짱구")
.password("1234")
.build();
String requestBody = mapper.writeValueAsString(userRequestDto);
HttpEntity<String> stringHttpEntity = new HttpEntity<>(requestBody, headers);
//when
ResponseEntity response = restTemplate.postForEntity(
"/validTest",
stringHttpEntity,
Void.class
);
//then 400 BAD_REQUEST가 확인되어야 정상
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
}
@DisplayName("이름 null 실패")
@Order(3)
@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(
"/validTest",
stringHttpEntity,
Void.class
);
//then 400 BAD_REQUEST가 확인되어야 정상
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
}
@DisplayName("비밀번호 빈 문자열 실패")
@Order(4)
@Test
public void 비밀번호_빈_문자열_실패() throws Exception {
UserRequestDto userRequestDto = UserRequestDto.builder()
.email("whitewise95@gmail.com")
.age(19)
.name("짱구")
.password("")
.build();
String requestBody = mapper.writeValueAsString(userRequestDto);
HttpEntity<String> stringHttpEntity = new HttpEntity<>(requestBody, headers);
//when
ResponseEntity response = restTemplate.postForEntity(
"/validTest",
stringHttpEntity,
Void.class
);
//then 400 BAD_REQUEST가 확인되어야 정상
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
}
}
400 BAD_REQUEST
전부 BedRequest로 확인된걸 볼 수 있다.
콘솔창
이메일 실패
2022-09-01 21:02:50.833 WARN 6864 --- [o-auto-1-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void com.example.examplecollection.valid.controller.ValidTestController.validTest(com.example.examplecollection.valid.dto.UserRequestDto): [Field error in object 'userRequestDto' on field 'email': rejected value [whitewise]; codes [Email.userRequestDto.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userRequestDto.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@e734180,.*]; default message [올바른 형식의 이메일 주소여야 합니다]] ]
나이 실패
... [생략] default message [age],19]; default message [19 이상이어야 합니다]] ]
이름 null 실패
... [생략] default message [name]]; default message [널이어서는 안됩니다]] ]
빈문자열 비밀번호 실패
... [생략] default message [password]]; default message [공백일 수 없습니다]] ]
200 OK
제약 검증에 만족하는 Data로 테스트를 했습니다.
콘솔창에 아래 데이터가 찍히는 걸 확인 할 수 있습니다.
로직 수행
UserRequestDto{email='whitewise95@gmail.com', password='1234', name='짱구', age=19}
@DisplayName("제약조건 성공")
@Order(5)
@Test
public void 제약조건_성공() throws Exception {
UserRequestDto userRequestDto = UserRequestDto.builder()
.email("whitewise95@gmail.com")
.age(19)
.name("짱구")
.password("1234")
.build();
String requestBody = mapper.writeValueAsString(userRequestDto);
HttpEntity<String> stringHttpEntity = new HttpEntity<>(requestBody, headers);
//when
ResponseEntity response = restTemplate.postForEntity(
"/validTest",
stringHttpEntity,
Void.class
);
//then 400 BAD_REQUEST가 확인되어야 정상
assertEquals(HttpStatus.OK, response.getStatusCode());
}
전체 코드
2부 - @Validated 예제
1부 [Spring] @Valid, @Validated 에 대해서 정리[1] - @Valid 예제 - 현재
2부 [Spring] @Valid, @Validated 에 대해서 정리[2] - @Validated 예제 - 보러가기
참고한 자료
'몰아 넣기' 카테고리의 다른 글
[자바/자료구조] Array(배열)과 LinkedList(연결 리스트) 그리고 ArrayList에 대한 정리[1] - 개념 및 배열 사용법 및 예제 (2) | 2022.09.03 |
---|---|
[Spring] @Valid, @Validated 에 대해서 정리[2] - @Validated 예제 (0) | 2022.09.01 |
[스프링] GlobalException 처리 방법 및 예제 정리[2] - CustomException 이용하기 (0) | 2022.09.01 |
[스프링] GlobalException 처리 방법 및 예제 정리[1](@ControllerAdvice, @ExceptionHandler, @RestControllerAdvice) (0) | 2022.08.30 |
[IT] 대칭키와 비대칭키에 대해서 (0) | 2022.08.25 |