@Valid

코딩냥이·2024년 9월 10일

Annotation

목록 보기
27/34

@Valid

@Valid 어노테이션은 스프링에서 객체의 유효성 검증을 수행하기 위해 사용되는 어노테이션입니다. 이는 Java Bean Validation API의 일부로, 스프링과 통합되어 사용됩니다.

기능

  • 메서드 파라미터나 필드의 유효성을 검증합니다.
  • 객체 그래프 내의 중첩된 속성들에 대해서도 재귀적으로 유효성을 검증합니다.
  • 검증 실패 시 MethodArgumentNotValidException을 발생시킵니다.

사용 방법

기본적인 사용 방법은 다음과 같습니다:

import javax.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @PostMapping("/users")
    public User createUser(@Valid @RequestBody User user) {
        // user 객체는 이미 유효성 검증을 통과한 상태
        return userService.createUser(user);
    }
}

public class User {
    @NotNull(message = "Name cannot be null")
    @Size(min = 2, max = 30, message = "Name must be between 2 and 30 characters")
    private String name;

    @Email(message = "Email should be valid")
    private String email;

    // getters and setters
}

주요 특징

  1. 선언적 검증: 어노테이션을 통해 검증 규칙을 선언적으로 정의할 수 있습니다.
  2. 다양한 검증 규칙: @NotNull, @Size, @Min, @Max, @Email 등 다양한 내장 검증 어노테이션을 제공합니다.
  3. 커스텀 검증: 사용자 정의 검증 어노테이션을 만들어 사용할 수 있습니다.
  4. 그룹 검증: 상황에 따라 다른 검증 규칙을 적용할 수 있는 그룹 기능을 제공합니다.

고급 사용법

1. 중첩 객체 검증

public class Order {
    @Valid
    private List<OrderItem> items;

    @Valid
    private Address shippingAddress;

    // other fields, getters and setters
}

2. 그룹 검증

public class User {
    @NotNull(groups = {BasicInfo.class, AdvancedInfo.class})
    private String name;

    @NotNull(groups = AdvancedInfo.class)
    private String email;

    // getters and setters
}

@PostMapping("/users/basic")
public User createBasicUser(@Validated(BasicInfo.class) @RequestBody User user) {
    // ...
}

@PostMapping("/users/advanced")
public User createAdvancedUser(@Validated(AdvancedInfo.class) @RequestBody User user) {
    // ...
}

3. 커스텀 검증 어노테이션

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AdultValidator.class)
public @interface Adult {
    String message() default "Must be an adult";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class AdultValidator implements ConstraintValidator<Adult, LocalDate> {
    @Override
    public boolean isValid(LocalDate value, ConstraintValidatorContext context) {
        return value != null && ChronoUnit.YEARS.between(value, LocalDate.now()) >= 18;
    }
}

예외 처리

@Valid 검증 실패 시 발생하는 MethodArgumentNotValidException을 처리하는 방법:

@RestControllerAdvice
public class ValidationExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, List<String>>> handleValidationErrors(MethodArgumentNotValidException ex) {
        List<String> errors = ex.getBindingResult().getFieldErrors()
                .stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());
        return new ResponseEntity<>(getErrorsMap(errors), new HttpHeaders(), HttpStatus.BAD_REQUEST);
    }

    private Map<String, List<String>> getErrorsMap(List<String> errors) {
        Map<String, List<String>> errorResponse = new HashMap<>();
        errorResponse.put("errors", errors);
        return errorResponse;
    }
}

테스트

@Valid 어노테이션이 적용된 컨트롤러를 테스트할 때:

@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    void whenInvalidInput_thenReturns400() throws Exception {
        User user = new User(null, "invalid-email");

        mockMvc.perform(post("/users")
                .contentType("application/json")
                .content(objectMapper.writeValueAsString(user)))
                .andExpect(status().isBadRequest());
    }
}

주의사항

  1. 성능 고려: 대량의 데이터나 복잡한 객체 그래프에 대한 검증은 성능에 영향을 줄 수 있습니다.
  2. 순환 참조: 객체 그래프에 순환 참조가 있는 경우 무한 루프에 빠질 수 있으므로 주의해야 합니다.
  3. 검증 로직 분리: 복잡한 검증 로직은 별도의 서비스 계층으로 분리하는 것이 좋습니다.

베스트 프랙티스

  1. 명확한 오류 메시지: 각 검증 규칙에 대해 명확하고 구체적인 오류 메시지를 제공하세요.
  2. 계층별 검증: 표현 계층과 도메인 계층에서 각각 적절한 검증을 수행하세요.
  3. 그룹 활용: 상황에 따라 다른 검증 규칙을 적용해야 할 때 그룹 기능을 활용하세요.
  4. 커스텀 검증: 비즈니스 규칙에 특화된 복잡한 검증은 커스텀 검증 어노테이션을 만들어 사용하세요.
  5. 테스트 케이스: 다양한 입력 시나리오에 대한 테스트 케이스를 작성하세요.

결론

@Valid 어노테이션은 스프링 애플리케이션에서 입력 데이터의 유효성을 보장하는 강력한 도구입니다. 이를 통해 애플리케이션의 안정성을 높이고, 잘못된 데이터로 인한 오류를 사전에 방지할 수 있습니다. 선언적 방식으로 검증 규칙을 정의할 수 있어 코드의 가독성과 유지보수성을 향상시키며, 복잡한 객체 구조에 대해서도 효과적인 검증이 가능합니다. 다만, 성능과 복잡성을 고려하여 적절히 사용해야 하며, 비즈니스 로직에 특화된 복잡한 검증은 별도의 로직으로 분리하는 것이 좋습니다.

연관 포스팅

@RequestBody
@ControllerAdvice
@RestController
@ExceptionHandler
@Validated

profile
HelloMeow~!

0개의 댓글