특정 필드에 대해 검증해야 할 경우가 발생할 수 있다.
예를 들어 회원가입 기능을 구현한다고 해보자. 이름(String)과 나이(Integer)는 값이 무조건 입력되어야 하는 필수요소라고 하자.
클라이언트의 실수로 값을 입력하지 않는 경우, 범위를 벗어나거나는 경우, 나이에 Integer가 아닌 String 타입의 값을 입력하는 경우 등 잘못된 상황이 발생할 수 있다.
이때 우리는 검증을 통해 클라이언트에게 잘못된 값을 입력하였다는 것을 알려줄 수 있어야 한다. 그래야 클라이언트는 올바른 값을 입력할 것이기 때문이다.
이는 BindingResult를 활용하면 해결할 수 있다. 하지만 FieldError나 ObjectError를 통해 매번 코드로 검증 기능을 작성하는 일은 상당히 번거로울 수 있다.
특히 특정 필드에 대해 타입 검증을 제외한 검증 로직은 대부분 빈 값인지 아닌지, 특정 범위를 벗어나는지 아닌지와 같은 매우 일반적인 로직일 것이다.
이때 Bean Validation을 활용하면 검증 로직을 구현할 필요 없이 어노테이션만으로 보다 편리하게 검증 로직을 적용할 수 있다.
Bean Validation은 검증 애노테이션과 여러 인터페이스의 모음이다.
JPA(표준 기술) - 하이버네이트(구현체)의 관계처럼 일반적으로 Bean Validation(표준 기술) - 하이버네이트 Validator(구현체)로 사용된다.
참고로 이름에 하이버네이트가 붙어있지만 ORM과는 관련이 없는 기술이다.
즉, 순수한 Bean Validation을 사용하는 방법도 있지만, JPA - 하이버네이트 관계처럼 Bean Validation 역시 Spring과 통합해서 더욱 간편하게 사용할 수 있도록 지원한다.
Bean Validation의 검증 순서는 다음과 같다.
- 먼저 각각의 필드에 타입 변환을 시도
- 타입 변환을 성공한 경우에만 추가적으로 Bean Validation 적용
위와 같은 회원가입 상황에서 입력 Form을 통해 Member의 정보를 입력받을 경우, 각각의 필드에 검증이 필요하다면 다음과 같이 Bean Validation의 어노테이션을 선언해주면 된다.
@Data
public class Member {
@NotEmpty
private String name;
@Min(1) @Max(100)
@NotNull
private Integer age;
}
name 필드는 아무런 값을 입력하지 않는 경우 잘못된 입력이라고 판단하고, age 필드는 1~100을 벗어나거나 Null 값을 입력하는 경우 잘못된 입력이라고 판단하게 된다.
Controller에서 다음과 같이 검증이 필요한 Member 객체 앞에 @Validated 어노테이션을 선언해주면 잘못된 값이 입력될 경우 BindingResult에 적절한 error를 넣어준다.
이때 주의해야 할 점은 BindingResult의 위치는 반드시 검증하는 Member 객체 뒤쪽에 와야 한다.
@Slf4j
@Controller
public class JoinController {
@GetMapping("/join")
public String joinForm(@ModelAttribute Member member) {
return "joinForm";
}
@PostMapping("/join")
public String join(@Validated @ModelAttribute Member member, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
log.info("검증 오류 발생 errors={}", bindingResult);
return "joinForm";
}
return "join";
}
}
일일이 검증 로직을 구현할 필요 없이 간단한 검증 로직은 Bean Validation 어노테이션을 적용하여 쉽게 해결할 수 있다.
Bean Validation은 @NotEmpty, @NotNull, @Min, @Max와 같이 다양한 어노테이션을 지원한다.
이들에 대해선 https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#section-builtin-constraints 를 참고하면 자세하게 알 수 있다.
마지막으로 @NotEmpty와 @NotNull의 차이점이다.
개발 초기에 Bean Validation의 정의에 대해 자세히 모른 상태로 사용했다가 실수한 내용이다.
두 어노테이션의 의미를 단순하게 생각해보면 빈 값을 검증하는 것으로 착각할 수 있다.
NotEmpty는 빈 값이 아니어야 한다는 의미인데 NotNull도 Null 값이 아니어야 한다는 의미이기 때문에 공백이 아니어야 한다는 동일한 의미로 해석할 수 있기 때문이다.
따라서 Integer 타입의 나이 변수에도 빈 값을 입력하면 안된다는 의미로 자연스럽게 @NotEmpty 어노테이션을 적용했지만 오류가 발생하였다.
처음에는 NotEmpty는 빈 값이 아니어야 한다는 의미니깐 Reference 타입의 모든 값에 적용돼야 하는 것이 아닌가?라는 의문을 가지면서 이해하지 못하였다.
하지만 이는 해당 어노테이션의 정의를 확인하면 이해할 수 있다.
빈 값이라는 의미를 가지는 어노테이션은 @NotBlank, @NotEmpty, @NotNull이 존재하며 이들에 대한 정의는 다음과 같다.
즉, @NotEmpty는 빈 값을 검증하긴 하지만, Integer 타입의 값을 지원하지 않기 때문에 오류가 발생하는 것이다.
[ Reference ]
· https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard
'IT 개인 공부 > Spring' 카테고리의 다른 글
[Spring] @PostConstruct에서 @Transactional 처리 시 문제점 (0) | 2021.09.17 |
---|---|
[Spring] 양방향 매핑시 주의점 : toString (0) | 2021.09.15 |
[Spring] AOP가 적용되지 않은 메서드에서 AOP가 적용된 메서드를 호출하면 AOP가 정상적으로 동작할까? (0) | 2021.09.11 |
[Spring] AOP란? (0) | 2021.09.09 |
[Spring] 서블릿 필터 vs 스프링 인터셉터 (0) | 2021.09.05 |
댓글