@Valid vs @Validated

오잉·2023년 5월 31일
2
post-thumbnail

controller로 들어온 request에 대해 검증할 때

    @PostMapping
    public ResponseEntity<LineCreateResponse> createLine(@Valid @RequestBody LineCreateRequest request) {
        Line createdLine = lineService.createNewLine(request.toDto());

        LineCreateResponse response = LineCreateResponse.from(createdLine);
        return ResponseEntity.created(URI.create("/lines/" + createdLine.getId())).body(response);
    }

이런식으로 검증하고자 하는 객체 앞에 @Valid를 붙여주기도 하지만,

    @PostMapping
    public ResponseEntity<LineCreateResponse> createLine(@Validated @RequestBody LineCreateRequest request) {
        Line createdLine = lineService.createNewLine(request.toDto());

        LineCreateResponse response = LineCreateResponse.from(createdLine);
        return ResponseEntity.created(URI.create("/lines/" + createdLine.getId())).body(response);
    }

이런식으로 @Validated를 붙여주기도 한다.

미션을 진행하다보니 이 두 어노테이션이 어떤 차이가 있는지 궁금해졌다.
이번 글에서는 @Valid@Validated의 차이에 대해 알아보려 한다!


1. @Valid와 @Validated의 공통점

둘 다 Validator를 이용해 객체의 제약 조건을 검증하도록 지시하는 어노테이션이다.

아마 다들 직접 Validator를 만들기 보다는 Bean Validation을 이용해 편하게 검증을 처리하는 것에 익숙할 것이다.
(스프링 부트는 LocalValidatorFactoryBean을 자동으로 글로벌 Validator로 등록한다.
이 Validator는 @NotNull과 같은 어노테이션을 보고 검증을 수행한다.)

참고) Bean Validation : 검증 로직을 모든 프로젝트에 적용할 수 있게 공통화하고, 표준화 한 것. 애노테이션만으로 검증 로직을 매우 편리하게 적용할 수 있다.

public class Item {
      private Long id;
      
      @NotBlank // "" or " " or null 불가
      private String itemName;
      
      @NotNull // null 불가
      @Range(min = 1000, max = 1000000) // 1000 <= x <= 1000000
      private Integer price;
      
      @NotNull
      @Max(9999) // x <= 9999
      private Integer quantity;
      
      public Item() {
	  }
      
      public Item(String itemName, Integer price, Integer quantity) {
            this.itemName = itemName;
            this.price = price;
            this.quantity = quantity;
      }
      ...
}

우리가 미션에서 자주 사용한 저 @NotNull, @NotBlank 등이 다 Bean Validation이 제공하는 어노테이션이다!

2. @Valid

  • JSR-303 표준 스펙(자바 진영 스펙)
  • 유효성 검증을 할 파라미터에 @Valid를 붙여줘야 진행된다.
	@Controller
	public class UserController {
		@PostMapping("/") 
		public String add(@Valid Request request) {
      		...
		}
	}
  • 특정 ArgumentResolver에 의해 처리된다
    • @RequestBody는 RequestResponseBodyMethodProcessor가 처리하며, 이 내부에서 @Valid로 시작하는 어노테이션이 있을 경우에 유효성 검사를 진행한다.
    • @ModelAttribute를 사용중이라면 ModelAttributeMethodProcessor에 의해 @Valid가 처리된다.
  • 그래서 컨트롤러에서만 동작 (컨트롤러 메소드의 유효성 검증만 가능)
  • 다른 계층에서는 검증x (다른 계층에서 사용하려면 @Validated와 결합해야한다)
  • 유효성 검증에 실패할 경우 MethodArgumentNotValidException 예외가 발생

3. @Valiated

  • JSR 표준 기술이 아니며 Spring 프레임워크에서 제공하는 어노테이션 및 기능
  • 클래스에 @Validated를 붙여주고, 유효성을 검증할 메소드의 파라미터에 @Valid를 붙여줘야 작동한다.
	@Service
	@Validated
	public class UserService {
		public void addUser(@Valid AddUserRequest addUserRequest) {
			...
		}
	}	
  • AOP 기반으로 메소드의 요청을 가로채서 유효성 검증을 진행
    • @Validated를 클래스 레벨에 선언하면 해당 클래스에 유효성 검증을 위한 AOP의 어드바이스 또는 인터셉터(MethodValidationInterceptor)가 등록
    • 해당 클래스의 메소드들이 호출될 때 AOP의 포인트 컷으로써 요청을 가로채서 유효성 검증을 진행
  • 그러므로 컨트롤러, 서비스, 레포지토리 등 계층에 무관하게 스프링 빈이라면 유효성 검증을 진행할 수 있다.
  • 유효성 검증에 실패할 경우 ConstraintViolationException 예외 발생
  • 동일한 클래스에 대한 제약조건이 요청이 따라 달라져야 할 때, 제약 조건이 적용될 검증 그룹을 지정할 수 있는 기능인 groups -> 근데 잘 안 씀

4. 그 외

1) 객체 내 객체 속성에 @Valid를 붙이게 되면, 객체 내 객체에 대한 검증도 진행할 수 있다.

2) @Validated를 붙이면 object가 아닌 parameter에 대한 검증도 할 수 있다.

2-1) controller 뿐 아니라 다른 계층에서도 사용 가능!

2-2) @RequestParam과 @PathVariable에 대한 검증에는 ConstraintViolationException 오류가 발생한다

@Validated기반이기 때문에~

2-3) @RequestBody, @ModelAttribute에 대한 검증에는 MethodArgumentNotValidException 오류가 발생한다

@Valid기반이기 때문에~

+) @RequestBody여도 @Validated를 클래스 레벨에 붙이면 ConstratinVoilationException이 발생하는지 확인해봤는데, 그대로 MethodArgumentNotValidException이 터졌다.


결론

  • Service나 기타등등 Bean에서 사용하기 위해서는 '@Validated'와 '@Valid'를 추가해야 한다.
	@Service
	@Validated
	public class UserService {
		public void addUser(@Valid AddUserRequest addUserRequest) {
			...
		}
	}	
  • Controller는 '@Validated'가 필요 없다. 검사를 진행할 곳에 '@Valid'를 추가하면 된다.
	@Controller
	public class UserController {
		@PostMapping("/") 
		public String add(@Valid Request request) {
      		...
		}
	}

참고자료

https://mangkyu.tistory.com/174
https://kdhyo98.tistory.com/81
https://bepoz-study-diary.tistory.com/413
https://kapentaz.github.io/spring/Spring-Boo-Bean-Validation-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90/#
https://doomdevlog.tistory.com/22
https://meetup.nhncloud.com/posts/223


나중에 좀 더 공부할 내용

profile
오잉이라네 오잉이라네 오잉이라네 ~

1개의 댓글

comment-user-thumbnail
2023년 6월 2일

정리 너무 잘해주셨네요.... 감동입니다

답글 달기