1. Bean Validation
참고 자료:
https://www.baeldung.com/spring-mvc-custom-validator
baeldung 을 읽으면서 따라해봄
3단계 과정
1. Annotation 만들기
2. Validator 만들기
3. 사용하기
나는 들어오는 값이 정해진 값 중의 하나인지 검증하고 싶었다
1) The New Annotation
@Documented
@Constraint(validatedBy = CategoryValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Category {
String message() default "Invalid category";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
그래서 이렇게 만들어줌
2) Creating a Validator
public class CategoryValidator implements ConstraintValidator<Category, String> {
@Override
public void initialize(Category category) {
}
@Override
public boolean isValid(String categoryEnum,
ConstraintValidatorContext cxt) {
return categoryEnum.equals(CategoryEnum.ALL.toString())
||categoryEnum.equals(CategoryEnum.ASIAN.toString())
||categoryEnum.equals(CategoryEnum.BURGER.toString())
||categoryEnum.equals(CategoryEnum.CHICKEN.toString())
||categoryEnum.equals(CategoryEnum.CHINESE.toString())
||categoryEnum.equals(CategoryEnum.JAPANESE.toString())
||categoryEnum.equals(CategoryEnum.KOREAN.toString())
||categoryEnum.equals(CategoryEnum.LUNCHBOX.toString())
||categoryEnum.equals(CategoryEnum.PIZZA.toString())
||categoryEnum.equals(CategoryEnum.SNACK.toString())
||categoryEnum.equals(CategoryEnum.WESTERN.toString())
;
}
}
3) Applying Validation Annotation , Controller에 적용
baeldung에서는 이렇게 사용한다!
@ContactNumberConstraint
private String phone;
@PostMapping("/addValidatePhone")
public String submitForm(@Valid ValidatedPhone validatedPhone,
BindingResult result, Model m) {
if(result.hasErrors()) {
return "phoneHome";
}
m.addAttribute("message", "Successfully saved phone: "
+ validatedPhone.toString());
return "phoneHome";
}
하지만 우리 프로젝트는 BindingResult 없이 GlobalExceptionHandler 에서 MethodArgumentNotValidException을 AOP로 처리를 해서 저 부분은 안 넣었다.
GlobalHandler로 에러처리를 하는 부분)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResponse<List<String>> handleMethodArgumentNotValidException(
BindingResult bindingResult) {
List<String> errors = bindingResult.getFieldErrors().stream()
.map((FieldError fieldError) -> fieldError.getField() +" "+ fieldError.getDefaultMessage())
.toList();
return new ApiResponse<>(HttpStatus.BAD_REQUEST.value(), "입력값이 잘못되었습니다", errors);
}
내가 프로젝트에 적용한 코드는 다음과 같다
DTO에 적용했다
public record PostCategoryRequest (
@Category
String category
){
}
4) PostMan으로 테스트해봄
되는 카테고리를 날리면 잘 됨
{
"category":"ALL"
}
{
"status": 200,
"message": "글 카테고리별 조회에 성공했습니다.",
"data": [
{
"id": 1,
"author": "가나다라",
"address": "주소주소",
"store": "storestore",
"minPrice": 20000,
"sumPrice": 5000,
"deadline": "2024-01-11T00:41:48"
},
{
"id": 2,
"author": "가나다라",
"address": "주소주소",
"store": "storestore",
"minPrice": 20000,
"sumPrice": 10000,
"deadline": "2024-01-11T00:41:49"
},
{
"id": 3,
"author": "가나다라",
"address": "주소주소",
"store": "storestore",
"minPrice": 20000,
"sumPrice": 5000,
"deadline": "2024-01-11T00:41:50"
}
]
}
존재하지 않는 카테고리를 날리면
{
"category":"PLS"
}
{
"status": 400,
"message": "입력값이 잘못되었습니다",
"data": [
"category Invalid category"
]
}
에러처리가 잘 된다!
2) Parameter Validation
RequestBody방식이 아니라 RequestParam 방식으로 변경해야 할 일이 있어서
DTO가 아니라 Parameter에서도 검증할 수 있을지 찾아보고 적용해봤다
1) Annotation을 정의할때 target에 PARAMETER를 추가해줌
@Documented
@Constraint(validatedBy = CategoryValidator.class)
@Target( {ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Category {
String message() default "has to be one of the CategoryEnum";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2) Validator는 Bean validation 때와 같다
public class CategoryValidator implements ConstraintValidator<Category, String> {
@Override
public void initialize(Category category) {
}
@Override
public boolean isValid(String categoryEnum,
ConstraintValidatorContext cxt) {
return categoryEnum.equals(CategoryEnum.ALL.toString())
||categoryEnum.equals(CategoryEnum.ASIAN.toString())
||categoryEnum.equals(CategoryEnum.BURGER.toString())
||categoryEnum.equals(CategoryEnum.CHICKEN.toString())
||categoryEnum.equals(CategoryEnum.CHINESE.toString())
||categoryEnum.equals(CategoryEnum.JAPANESE.toString())
||categoryEnum.equals(CategoryEnum.KOREAN.toString())
||categoryEnum.equals(CategoryEnum.LUNCHBOX.toString())
||categoryEnum.equals(CategoryEnum.PIZZA.toString())
||categoryEnum.equals(CategoryEnum.SNACK.toString())
||categoryEnum.equals(CategoryEnum.WESTERN.toString())
||categoryEnum.equals("CAFE")
;
}
}
3) Controller의 Parameter에 annotation 적용
//글 카테고리별 조회
@GetMapping("/posts/category/page/{page}")
public ApiResponse<List<BriefPostResponse>> getPostsByCategory(
@AuthenticationPrincipal UserDetailsImpl userDetails,
@PathVariable(name = "page") int page,
@RequestParam(name = "category") @Category String category
) {
return new ApiResponse<>(HttpStatus.OK.value(), "글 카테고리별 조회에 성공했습니다.",
postReadService.getPostsByCategory(page, category, userDetails.getUser()));
}
'TIL(Develop) > Spring' 카테고리의 다른 글
[Kotlin] RestClient를 MSA로 구성된 Spring module간 통신에 사용하는 예시 (0) | 2024.04.25 |
---|---|
HTTP Interface Client, (RestClient, WebClient, RestTemplate의 차이) (0) | 2024.04.25 |
Spring 프로젝트 HTTP에서 HTTPS로 변경하기 (Java로 SSL 만들어서 Spring 프로젝트에 적용하기(P12 방식)) (0) | 2024.04.17 |
📖Entity에 복합키로 id구성하는 방법 @IdClass, @EmbeddedId (0) | 2024.03.15 |
📖Hibernate/JPA의 id 생성 전략들 (0) | 2024.01.17 |