티스토리 뷰
(참고: Spring Boot와 Thymeleaf, IntelliJ를 기준으로 작성된 글입니다)
이전 글(Validation)에서 살펴보았던 Validation의 경우 코드를 직접 작성하여 테스트하였는데
검증이란 것이 특정 서비스에서만 적용이 되는 것이 아닌 만국 공통의 개념인 만큼 스프링 프레임워크가 이를 제공하는데
여기서 더 나아가 애노테이션 기반의 검증기인 Bean Validation(JSR-380)을 사용할 수 있다
Bean Validation 이란
검증을 매번 코드로 작성하기에는 너무 번거롭다는 단점이 있다, 또한 각 계층별로 검증이 필요하기 때문에 동일한 검증 코드를 작성하게 된다면 구현된 검증 로직 간 불일치로 인하여 오류가 발생할 수도 있다.
여기에 주된 검증의 내용이 빈 값을 검증하거나, 값의 범위를 설정하거나 하는 우리의 생각을 크게 벗어나지 않는 검증의 범주이기 때문에, 이러한 검증 내용들을 애노테이션을 활용하여 도메인 모델 자체에 묶어 간편하게 사용하게 해주는 것이 바로 Bean Validation이다.
Bean Validation을 사용하기 위해서는 아래와 같이 스타터를 추가해주어야 한다
implementation 'org.springframework.boot:spring-boot-starter-validation'
스타터를 추가해주면 스프링이 자동으로 Bean Validation을 인지하고 스프링에 통합한다
@Annotaition
Bean Validation은 애노테이션 형식의 검증기를 제공하는 만큼 굉장히 다양한 검증을 제공한다
본 글에서는 각각의 애노테이션에 대한 설명은 생략하고 Annotation을 사용방법을 살펴보고자 한다
(공식문서의 Annotation 예제 : https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/#section-builtin-constraints)
@Data
public class Item {
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min = 100, max = 100000)
private Integer price;
@NotNull
@Max(100)
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
위의 예시와 같이 도메인 모델에 검증하고자 하는 애노테이션을 추가해주면 끝이다
이후 컨트롤러 단에서 아래와 @Validated를 적어주면 컨트롤러를 호출할 시 자동으로 Bean Validation이 작동하게 된다
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
}
이것이 가능한 이유는 스프링 부트가 LocalVaidatorFoctoryBean이라는 글로벌 Validator를 등록하여 이후 애노테이션으로 되어 있는 검증 값들을 자동으로 검증하여 검증 오류가 발생하면 FieldError와 ObjectError를 생성하여 BindingResult에 담아주게 된다
• 검증 순서
주의해야 할 점이 하나 있는데, Bean Validation은 값이 제대로 되어있는지 검증하는 개념이다, 만약 int 타입에 String이 들어온다며 애초에 타입이 잘못되어 있기 때문에 Bean Validation을 적용하지 않는다, 즉 객체에 바인딩을 성공한 필드만 Bean Validation을 적용시키는 것이다
Bean Validation Errorcode
검증에서 데이터 그 자체를 검증하는 것도 매우 중요하지만 클라이언트(사용자)에게 어떠한 값이 잘못되었는지에 대한 정보를 제공하는 것도 매우 중요하다, 이전 글 (Validation)에서 BindingResult가 에러코드를 생성하는 규칙을 활용하여 스프링 메시지로 오류 메시지를 일관 관리하는 법을 살펴보았다, Bean Validation의 경우 어떻게 이 에러코드를 생성하는지 살펴보자
• Field Error
Item이라는 객체와 itemName라는 필드 값을 가진 @NotBlank의 에러 코드는 아래와 같이 생성된다
NotBlank.item.itemName = 애노테이션 + "." + object + "." + field
NotBlank.itemName = 애노테이션 + "." + field
NotBlank.java.lang.String = 애노테이션 + "." + fieldType
NotBlank.itemName = 애노테이션
다른 애노테이션을 사용한다고 해도 동일한 생성 규칙을 따라 생성이 되기 때문에
해당 에러코드를 스프링 메시지를 활용하여 작성해주면 매우 편리하게 클라이언트에게 반환하는 오류 메시지를 관리/추가할 수 있다
• Object Error
Bean Validation에서 Object Error를 위한 ScriptAssert라는 애노테이션을 지원한다
@Data
@ScriptAssert(lang = "javascript", script = "_this.price * _this.quantity >=10000") //this는 해당 객체의 필드를 가르킨다
public class Item {
//...
}
하지만 ScriptAssert가 제공하는 기능이 비교적 단순하기 때문에, 복잡한 검증 기능을 대응하기 어렵다고 한다, 따라서 Object 오류의 경우에 굳이 @ScriptAssert를 사용하는 것보다 오브젝트 오류 관련 부문만 직접 자바 코드로 작성하는 것이 더 좋다고 한다
Seperation Form Object
매우 간편한 Bean Validation이지만 한 가지 문제점이 있다, 예를 들어 회원가입과 회원수정의 검증 로직이 다르다면 하나의 애노테이션으로 처리할 수 없을 것이다, 이에 Bean Validation은 groups란 기능을 제공하지만, 대부분 groups가 아닌 폼 객체를 분리하여 사용하는 방식을 사용한다고 하니, 폼 객체 분리 방식만 살펴보고자 한다.
사실 매우 간단한데 기존의 도메인과 별도로 해당 데이터를 전송하고 업데이트하는 Form 객체를 만들어 분리시키는 것이다
예시와 같이 살펴보자
//회원가입시 데이터 전송 폼
@Data
public class MemberSignupForm {
private Long id;
@NotNull
private String userId;
@NotNull
@Size(min = 6)
private String userPw;
}
//회원수정시 데이터 전송 폼
@Data
public class MemberUpdateForm {
@NotNull
private Long id;
@NotNull
private String userId;
@NotNull
@Size(min = 6)
private String userPw;
}
서버 사이드에서 생성하는 사용자를 구별하기 위하여 자동으로 생성하는 id가 존재한다고 했을 때, 회원 가입 시에는 id가 아직 생성이 되어 있지 않기 때문에 @NotNull이라는 제약조건을 주게 되면 에러가 발생한다 이에 회원가입 시에는 null값을 허용하고, 회원 수정 시에는 이미 id가 생성되어 있기 때문에 클라이언트 사이드에서 값을 조작하여 보내는 경우를 방지하기 위하여, @NotNull이라는 제약조건을 주었다
이후에 컨트롤러에서 받는 객체만 달리 해주면 각각의 BeanValidation이 작동하여 각 객체마다 서로 다른 검증을 거칠 수 있다
//회원가입 컨트롤러
public String signup(@Validated @ModelAttribute MemberSignupForm form,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
//...
}
//회원수정 컨트롤러
public String signup(@PathVarialbe Long id, @Validated @ModelAttribute MemberUpdateForm form,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
//...
}
Bean Validation HTTP Body
쿼리 스트링 혹은 Form 말고도 HTTP Body를 통한 API JSON을 사용할 때도 물론 검증이 필요하다
HTTP Body의 경우 어떻게 검증하여 반환하는지 살펴보고자 한다
우선 검증하는 방법은 동일하게 해당 객체로 받아오면 된다
@RequestBody
public String signup(@PathVarialbe Long id, @Validated @ModelAttribute MemberUpdateForm form,
BindingResult bindingResult) {
//...
return bindingResult;
}
API는 3가지의 경우가 발생한다 (ModelAttribute에 바인딩 실패 예시를 생각하면 되겠다)
1. 성공 요청 : 성공
2. 실패 요청 : JSON을 객체로 생성하는 것 자체가 실패함
3. 검증 오류 요청 : JSON을 객체로 생성하는 것은 성공, 검증에서 실패
ModelAttribute는 필드 단위로 정교하게 바인딩이 적용되어, 특정 필드가 바인딩되지 않아도 나머지 필드는 바인딩되는 경우와 달리 HTTP Body를 반환할 때 사용하는 @RequestBody는 HttpMessageConverter의 경우 필드 단위가 아닌 전체 객체 단위로 적용이 된다, 따라서 메시지 컨버터의 작동이 성공해서 해당 객체를 만들어야 Bean Validation의 @Validated가 적용된다
즉 객체를 만들지 못하면 컨트롤러 자체가 호출되지 않고 그전에 예외가 발생하는 것이다
핵심정리
계층마다 모두 필요한 검증의 경우 Bean Validation을 사용하여 도메인 모델로 묶어 애노테이션으로 각종 검증기능을 사용할 수 있다
하지만 이를 하나의 객체로 사용하기에는 각각의 개별 로직을 적용하기 어렵기 때문에 대부분 데이터를 전송하기 위한 별도의 폼을 만들어 해당 데이터를 해당 검증 로직으로 검증을 한다
개인 학습을 위해 작성되는 글입니다.
제가 잘못 알고 있는 점에 대한 지적 / 더 나은 방향에 대한 댓글을 환영합니다.
참조 링크:
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard
https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/
'Spring > Spring MVC' 카테고리의 다른 글
[Spring MVC] Filter & Interceptor (0) | 2021.09.10 |
---|---|
[Spring MVC] Login with Cookie & Session (0) | 2021.09.09 |
[Spring MVC] Validation & Error Messages(검증 & 에러 메세지) (0) | 2021.09.07 |
[Spring MVC] Message & Locale (0) | 2021.09.03 |
[Spring MVC] PRG (Post/Redirect/Get) 패턴 (0) | 2021.08.31 |
- Total
- Today
- Yesterday
- 쿠키
- ExceptionHandlerExceptionResolver
- 캐시
- 제이쿼리 인접 관계 선택자
- Session
- DefaultHandlerExceptionResolver
- Spring TypeConverter
- 제이쿼리란
- uri
- jQuery 직접 선택자
- 제이쿼리 기본 선택자
- application/x-www-form-urlencoded
- 세션
- @ExceptionHandlere
- Spring Container
- OOP
- http
- Cache
- 제이쿼리 위치탐색선택자
- @ResponseStatus
- 제이쿼리 탐색선택자
- maenco
- 맨코
- spring
- Spring MVC
- cookie
- DTO와 VO의 차이
- ResponseStatusExeceptionResolver
- 제이쿼리 직접 선택자
- Spring API Error
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |