이전 [[8. 컨트롤러 구현(API)|컨트롤러 구현 코드]]에는 API 기능 구현에 집중했기 때문에 사용자의 데이터를 검증하는 코드를 작성하지 않았다. 그러니 이번에는 사용자의 데이터를 검증하는 코드를 추가해보자. 검증 코드는 컨트롤러에 추가할 것이고 JSR-303과 Spring Validator를 사용해볼 것이다. ## 참고 : [[G. JSR-303 @Annotation 목록|주요 JSR-303 @Annotation]] ## 참고 : [@Valid 동작 원리](https://incheol-jung.gitbook.io/docs/q-and-a/spring/valid) ## 의존성 설정 ```groovy dependencies { implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' } ``` ## JSR-303 JSR-303은 자바 빈의 유효성 검사를 위한 표준 스펙이다. 어노테이션을 통해 쉽게 자바 빈의 유효성 검사를 할 수 있어 자주 사용된다. ### `PromptTemplateRequest` 수정 ```java @Getter @Setter @AllArgsConstructor @NoArgsConstructor @ToString @Builder public class PromptTemplateRequest { @NotNull(message = "ID is required") @Length(min=1, max = 20, message = "ID length must be between 1 and 20 characters") private String id; @NotNull(message = "Content is required") @Length(min=1, max = 1000, message = "Content length must be between 1 and 1000 characters") private String content; public PromptTemplate toEntity(){ return PromptTemplate.builder() .id(id) .content(content) .build(); } } ``` - `id`와 `content`는 모두 null이면 안 되고 길이 제한이 존재한다. 그래서 `@NotNull`과 `@Length`를 추가하였다. - 위 어노테이션만으로는 검증이 작동하지 않고 컨트롤러에 따로 어노테이션을 더 추가해야만 한다. ### `ChatGPTController` 수정 ```java @PutMapping("/prompt-template/{id}") public ResponseEntity<PromptTemplateResponse> updatePromptTemplateById( @PathVariable("id") String id, @Valid @RequestBody PromptTemplateRequest promptTemplateRequest, BindingResult bindingResult) { if (bindingResult.hasErrors()){ System.out.println( Objects.requireNonNull( bindingResult.getFieldError() ).getDefaultMessage() ); throw new BadRequestException( bindingResult.getFieldError().getDefaultMessage() ); } ... (생략) } ``` - 검증하고자 하는 `PromptTemplateRequest` 인자에 `@Valid` 어노테이션을 부착한다. 이 어노테이션을 부착하지 않으면 검증 코드가 작동하지 않는다. - `BindingResult`는 검증 결과를 데이터로 바인딩해주는 인터페이스로 `@Valid`이 붙은 인자 바로 다음 순서에 위치해야 한다. - 컨트롤러에서 예외 처리를 하지 않고 `BadRequestException`을 던지는 이유는 `@ControllerAdvice`에서 일괄적으로 예외 처리를 할 것이기 때문이다. ## Spring Validator JSR-303의 어노테이션을 이용한 검증 방법은 간단해서 좋다. 하지만 검증 과정이 복잡해질 수록 이런 방법을 적용하기 힘들어진다. 스프링에서는 이런 경우 Spring Validator를 통해 개발자가 직접 검증 과정을 구현할 수 있다. 컨트롤러에서 `@InitBinder`를 통해 Validator를 추가해주기만 하면 기존 JSR-303과 동일한 코드로 작동하기 때문에 편리하다. ### `o.s.validation.Validator` ```java public interface Validator { boolean supports(Class<?> clazz); void validate(Object target, Errors errors); } ``` - Spring Validator를 사용하려면 위 인터페이스를 구현해야 한다. - `Validator::supports`는 `@Valid`를 단 자바 빈이 검증 대상인지 확인하는 메서드다. - `Validator::validate`는 검증 대상을 검증하고 문제가 있는 경우 에러를 반환하는 메서드다. ### `PromptTemplateValidator` #### 클래스 생성 ```java public class PromptTemplateValidator implements Validator { } ``` - `o.s.validation.Validator`를 구현한다. #### `supports` 메서드 구현 ```java @Override public boolean supports(Class<?> clazz) { return PromptTemplateRequest.class.equals(clazz); } ``` - 자바 빈이 검증 대상인 `PromptTemplateRequest`에 해당하는지 확인한다. #### `validate` 메서드 구현 ```java @Override public void validate(Object target, Errors errors) { PromptTemplateRequest request = (PromptTemplateRequest) target; if (Objects.isNull(request.getId())) { errors.rejectValue("id", "required", "ID is required"); return; } if (Objects.isNull(request.getContent())) { errors.rejectValue("content", "required", "Content is required"); return; } if (request.getId().isEmpty() || request.getId().length() > 20) { errors.rejectValue("id", "length", "ID length must be between 1 and 20 characters"); return; } if (request.getContent().isEmpty() || request.getContent().length() > 1000) { errors.rejectValue("content", "length", "Content length must be between 1 and 1000 characters"); } } ``` - 검증 내용은 JSR-303을 사용할 때와 같다. ### `ChatGPTController` 수정 ```java @RestController @Slf4j public class ChatGPTController { @InitBinder void initBinder(WebDataBinder webDataBinder){ webDataBinder.addValidators(new PromptTemplateValidator()); } ... (생략) @PutMapping("/prompt-template/{id}") public ResponseEntity<PromptTemplateResponse> updatePromptTemplateById( @PathVariable("id") String id, @Valid @RequestBody PromptTemplateRequest promptTemplateRequest, BindingResult bindingResult) { if (bindingResult.hasErrors()){ System.out.println( Objects.requireNonNull( bindingResult.getFieldError() ).getDefaultMessage() ); throw new BadRequestException( bindingResult.getFieldError().getDefaultMessage() ); } ... (생략) ``` - `@InitBinder`로 Validator를 추가한다. 추가한 Validator는 디스패처 서블릿이 컨트롤러를 호출할 때 자동으로 호출되어 유효성 검사를 진행한다. - `@Valid`나 `BindingResult` 나 `BadRequestException`는 JSR-303과 동일하게 적용된다. ## [[4-4. 컨트롤러 테스트 코드(검증 & 예외처리)|테스트 코드]] ![[4-4. 컨트롤러 테스트 코드(검증 & 예외처리)#`ValidationAndExceptionTest` 작성]]