-
[Java] Bean Validation에 대해서 알아보자 (JSR-303, JSR-380, 파라미터 유효성 체크)백엔드/Spring 2022. 11. 6. 21:57
Jakarta bean validation 서비스에서 중요한 기술 중 하나가, Validation을 체크하는 기술입니다.클라이언트에서 서버사이드로 값을 전달할 때, 올바른 값이 전달되었는지, Validation을 체크 할 수 있어야 합니다.이러한 기술을 위해서, Java Community Process (JCP)에서는 “Bean Validation”이라는 스펙을 공개했습니다.이와 같은 기술을 통해서, 개발자는 Annotation을 사용하여, Validation을 체크할 수 있었습니다.이 방법의 Specification을 기술한 라이브러리가 Jakarta Bean Validation 이고이를 구현한 라이브러리가 바로, Hibernate Validator입니다.🔆 Specification
Java Bean Validation Specification- JSR303
- JSR380
Java Bean Validation’s Name- “javax.validation” (Spring Boot 2.x)
- “jakarta.validation” (Spring Boot 3.x)
Reference Implementation
🧩 Dependency
Spring Boot에서 “spring-boot-starter-validation”은 어떤 구조로 되어 있는지 보겠습니다.다음과 같은 구조를 통해서, 의존성을 가져옵니다.- spring-boot-starter-validation
- hibernate Validation (Implementation)
- jakarta.validation (Specification)
- hibernate Validation (Implementation)
코드 레벨에서는 아래와 같은 의존성을 가질 수 있습니다.Gradleimplementation 'org.springframework.boot:spring-boot-starter-validation'
Maven<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
✏️ Usage
이제 간단한 예시를 토대로, 확인해보겠습니다.[POST] 영화 정보를 추가하는 API를 예시로 들었습니다.
1. Request에 대한 DTO를 생성
MovieAddRequest.java@Data public class MovieAddRequest { @NotBlank(message = "MovieAddRequest.name은 빈 값일 수 없습니다") private String name; @NotBlank(message = "MovieAddRequest.category 빈 값일 수 없습니다") private String category; @NotBlank(message = "MovieAddRequest.useYn 빈 값일 수 없습니다") private String useYn; }
2. Response에 대한 DTO 생성
기본이 될 Base DTO 입니다.ApiBase.java@Builder @Getter public class ApiBase<T> { private String message; private T response; }
영화 추가에 대한 응답 DTO 입니다.MovieAddResponse.java@Data public class MovieAddResponse { private String name; }
3. Validation를 확인할 Controller 생성
ValidController.java@RestController public class ValidController { @PostMapping("/") public ResponseEntity<ApiBase> addMovie(@RequestBody @Valid MovieAddRequest request){ MovieAddResponse response = new MovieAddResponse(); response.setName(request.getName()); return ResponseEntity.status(HttpStatus.CREATED).body(ApiBase.builder().message("").response(response).build()); } }
4. 전역으로 에러를 핸들링할, GlobalErrorHandler 생성
실제로 Validation 체크에 실패한 경우, Exception이 발생하는 정보에 대해서Custom Exception 처리를 해줘야 합니다.GlobalErrorHandler.java@ControllerAdvice @Slf4j public class GlobalErrorHandler { @ExceptionHandler({MethodArgumentNotValidException.class}) public ResponseEntity<ApiBase> handleResponseBodyError(MethodArgumentNotValidException ex) { log.error("Exception Caught in handleRequestBodyError : {}", ex.getMessage()); var error = ex.getBindingResult().getAllErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .sorted() .collect(Collectors.joining(", ")); log.error("Error is : {}", error); return ResponseEntity.badRequest() .body(ApiBase.builder().message(error).response("").build()); } }
Validation 체크에 실패한 경우 GlobalErrrorHandler에서, Http Status는 400 (Bad Request)가 되며Annotation에 설정한 메시지들이 Body에 출력이 되어야 합니다.⏰ Execute
이제 완성이 되었으면, localhost:8080/에 아래를 던져봅시다.curl --location --request POST 'http://localhost:8080/' \ --header 'Content-Type: application/json' \ --data-raw '{ "name": "제목", "category": "", "useYn": "N" }'
아래처럼, Validation 체크를 통과하지 못했고, 오류가 발생했습니다. (category가 빈 값으로 @NotBlank, 예외발생)위에서 설정한 GlobalErrorHandler에 의해서 핸들링 된 값입니다.ojaeseocBookPro:valid ojaeseong$ curl --location --request POST 'http://localhost:8080/' \ > --header 'Content-Type: application/json' \ > --data-raw '{ > "name": "제목", > "category": "", > "useYn": "N" > }' {"message":"MovieAddRequest.category 빈 값일 수 없습니다","response":""}
📎 Github Link
이번 포스팅에서 개발한 예제 코드에 대한 Github 링크입니다.https://github.com/rojae/Spring-Validation-Check-Sample/tree/v1GitHub - rojae/Spring-Validation-Check-Sample
Contribute to rojae/Spring-Validation-Check-Sample development by creating an account on GitHub.
github.com
🚀 Next Step
우리가 작성했던 MovieAddRequest.java에서 불만인 부분이 있습니다.movieCategory와 useYn은 정해진 값만 들어와야 합니다.(movieCategory는 열거형 타입이며, useYn은 "Y"이거나 "N"이여야만 합니다)@NotBlank(message = "MovieAddRequest.category 빈 값일 수 없습니다") private String category; @NotBlank(message = "MovieAddRequest.useYn 빈 값일 수 없습니다") private String useYn;
물론, Enum으로 설정하고, 기동을 하면 오류가 발생하기야 하지만..Validation 체크가 아니고 맵핑, 파싱 오류입니다. 😰(Servlet Container's Mapping → Validation 순서로 인한 예상 밖의 에러 발생)그렇다면, 어떻게 하면 좋을까요? 🥺저의 해결법은, String으로 설정하고 자체적으로 Validator를 개발하는 방법입니다.이는 Bean Validation에서 제공하는 인터페이스를 통해서 구현이 가능합니다. (ConstraintValidator 사용)이 과정을 거치면, 아래의 코드로 변환이 됩니다.@NotBlank(message = "MovieAddRequest.name은 빈 값일 수 없습니다") private String name; @MovieCategoryValid(message = "MovieAddRequest.category이 유효한 값이 아닙니다") private String category; @StringValid(acceptedList = {"Y", "N"}, message = "MovieAddRequest.useYn이 유효한 값이 아닙니다") private String useYn;
해당 글은 여기까지 입니다.심화 내용은 다음 포스팅에서 다루겠습니다. 😁
💬 Reference, Link
반응형'백엔드 > Spring' 카테고리의 다른 글
[Spring] 스케줄러를 활용한 이메일 발송 서버 샘플 코드 (0) 2023.11.15 [Spring] Validation 체크를 커스텀 개발해보자 (ConstraintValidator, 초기화에 대한 궁금증) (0) 2022.11.07 [Spring] Springboot Properties 설정파일을 외부에서 가져오자 (0) 2022.08.11 JPA Entity 생성시 Table Character set - UTF8 미설정 이슈 (0) 2022.08.11