2024년 11월 10일
조회 : 55

@Valid와 @Validate로 해결하는 Validation 처리

1. 👿 문제 상황

1. 🖼️ 프론트엔드에만 의존하는 Validation
프론트에서만 validation이 이루어지는 상황에서는 JavaScript로 직접 form을 전송하거나 input 검사가 제대로 작동하지 않으면 백엔드의 별도 처리가 없다면 데이터가 그대로 넘어가 서비스가 실행됩니다.
2. ✍️ 백엔드 검증의 부족
백엔드에서 기본적인 검증조차 이루어지지 않아 필수 필드가 누락될 경우 NullPointerException(NPE), 심지어는 DB Exception과 같은 오류가 발생합니다. 이는 서비스의 안정성을 크게 저해할 수 있습니다. 뿐만 아니라 아무리 간단한 쿼리라도 디비에 불필요한 부하(query exception or 검증되지 않은 데이터 insert)를 가하는 것입니다.

2. 🧶 문제 해결 과정

1) @Valid를 DTO에 추가
각 DTO 필드에 @NotBlank, @Size, @Pattern과 같은 제약 조건을 설정하고 각각의 오류 메시지를 함께 추가했습니다.
예를 들어:
java
1@NotBlank(message = "문의유형을 확인해 주세요.")
2private String type;
3
4@NotBlank(message = "제목을 확인해 주세요.")
5@Size(max = 50)
6private String title;
7
8@NotBlank(message = "내용을 확인해 주세요.")
9@Size(min = 10, max = 1500)
10private String contents;
11
12@NotBlank
13@Pattern(regexp = "^\\d+$", message = "전화번호는 숫자만 입력 가능합니다.")
14private String phoneNumber;
2) @Valid와 컨트롤러에서의 적용
컨트롤러 메서드에서 @Valid를 사용하여 전달된 DTO 객체가 유효성 검사를 통과하는지 확인했습니다. 유효성 검사를 통과하지 못하는 경우 프론트엔드에 에러 코드를 반환하여 사용자가 어떤 입력을 수정해야 하는지 알 수 있게 했습니다.
예시:
java
1
2@PostMapping("/inquiry")
3public ResponseEntity<?> createInquiry(@Valid @RequestBody InquiryDTO inquiryDTO) {
4    // 서비스 로직 수행
5    return ResponseEntity.ok("문의가 등록되었습니다.");
6}
7
3) 🌎 controllerAdvice 적용
전역적으로 설정한 메시지를 반환하기 위해 @ControllerAdvice를 활용하여 전역 예외 처리를 추가합니다.
java
1@ControllerAdvice
2public class GlobalExceptionHandler {
3
4    // @Valid 유효성 검사 실패 처리
5    @ExceptionHandler(MethodArgumentNotValidException.class)
6    public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
7        Map<String, String> errors = new HashMap<>();
8
9        // 유효성 검사 실패한 필드와 메시지를 맵에 담음
10        for (FieldError error : ex.getBindingResult().getFieldErrors()) {
11            errors.put(error.getField(), error.getDefaultMessage());
12        }
13
14        return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
15    }
16}
이제 프론트엔드에 일관된 JSON 형태의 오류 응답을 제공합니다.
📉 반환값
json
1{
2  "type": "문의유형을 확인해 주세요.",
3  "title": "제목을 확인해 주세요.",
4  "contents": "내용을 확인해 주세요.",
5  "phoneNumber": "전화번호는 숫자만 입력 가능합니다."
6}
4) @Pattern 직접 구현해보기
@Valid는 DTO 레벨에서 각 필드에 지정된 제약 조건을 검증하고 에러 메시지를 생성하여 사용자에게 알려주는 데 주로 사용됩니다.
예를 들어 특수문자를 거르는 어노테이션을 직접 만들어봅니다.
java
1
21. 어노테이션 인터페이스 정의
3
4@Documented
5@Constraint(validatedBy = NoSpecialCharactersValidator.class)
6@Target({ ElementType.FIELD, ElementType.PARAMETER })
7@Retention(RetentionPolicy.RUNTIME)
8public @interface NoSpecialCharacters {
9    String message() default "특수 문자는 사용할 수 없습니다.";
10    Class<?>[] groups() default {};
11    Class<? extends Payload>[] payload() default {};
12}
13
14
152. 검증 로직 구현
16
17public class NoSpecialCharactersValidator implements ConstraintValidator<NoSpecialCharacters, String> {
18    private static final String SPECIAL_CHAR_REGEX = ".*[^a-zA-Z0-9].*"; // 특수문자를 포함하는 패턴
19
20    @Override
21    public boolean isValid(String value, ConstraintValidatorContext context) {
22        if (value == null) {
23            return true; // null은 허용
24        }
25        return !value.matches(SPECIAL_CHAR_REGEX); // 특수문자가 포함되면 false 반환
26    }
27}
28
293.	사용 예시:
30
31@NoSpecialCharacters(message = "특수 문자가 포함된 제목은 사용할 수 없습니다.")
32private String title;
물론 간단한 정규식의 경우 @Pattern으로 대체할 수 있습니다.
java
1@Pattern(regexp = "^[a-zA-Z0-9]*$", message = "특수 문자가 포함된 제목은 사용할 수 없습니다.")
2private String title;

3. @Valid vs @Validated: 차이점 및 활용 방안

☕️ @Valid

@Valid는 Java Bean Validation의 표준으로 DTO, 엔티티의 필드에 유효성 검사를 적용하는 데 주로 사용됩니다.
컬렉션이나 객체의 중첩된 필드에 대해 재귀적으로 유효성 검사를 수행합니다.
🧐 유효성 검사 실패 시 MethodArgumentNotValidException이 발생

🍃 @Validated

@Validated는 Spring Framework에서 제공하는 어노테이션으로 특정 그룹을 설정할 수 있습니다.
주로 서비스 계층에서 유효성 검사를 수행할 때 사용하며 메서드 파라미터에서도 유효성 검사를 적용할 수 있습니다.
🧐 유효성 검사 실패시 ConstraintViolationException이 발생

예시

@Valid 예시
컨트롤러 레벨에서 DTO 유효성 검사를 적용할 때 주로 사용합니다:
java
1@PostMapping("/create")
2public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO userDTO) {
3    // 유효성 검사를 통과하면 로직을 수행
4    return ResponseEntity.ok("User created");
5}
@Validated 예시
서비스 계층에서 메서드 단위로 유효성 검사를 적용할 때 유용하며 특정 조건별로 검증 그룹을 설정할 수 있습니다.
java
1@Service
2@Validated
3public class UserService {
4
5    public void createUser(@Validated(UserGroup.Create.class) UserDTO userDTO) {
6        // 유효성 검사를 통과하면 로직을 수행
7    }
8}
@Validated와 그룹을 함께 사용하여 다양한 유효성 검사 조건을 적용할 수 있습니다.

4. 결과

이 과정을 통해 백엔드에서 일관된 유효성 검사를 구현하여 데이터 누락이나 잘못된 입력으로 인한 오류를 방지하고 사용자 경험을 향상시킬 수 있었습니다.
특히 백엔드에서 에러 메시지를 맞춤형으로 전달하여 프론트엔드에서 상황에 맞는 메시지를 보여줄 수 있도록 개선했습니다.
수정삭제