검색결과 리스트
글
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 10
인프런 강의 48일차.
- 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 1 (김영한 강사님)
- 1편에서 배운 MVC를 활용할 수 있는 기술 습득
- 타입 컨버터, 파일 업로드, 활용, 쿠키, 세션, 필터, 인터셉터, 예외 처리, 타임리프, 메시지, 국제화, 검증 등등
* intelliJ 단축키
- 사용할 클래스의 생성자 살펴보기 단축키 : Ctrl + P (Windows)
- for문 자동 생성 : iter + tab -> for(String msg : msgList) 가 자동 생성됨
- for문 내에서 필드 출력 : soutv -> System.out.println("msg = " + msg);가 자동 생성됨
5.1 Bean Validation 소개
- 검증 기능을 매번 코드로 작성하는 것은 상당히 번거롭다..
- 일반적으로 특정 필드에 대한 검증은 대부분 빈 값인지, 특정 범위에 속하는지와 같은 일반적인 로직이 대부분이다.
- 이러한 검증을 애노테이션을 사용해 처리하는 것이 Bean Validation
- ex) @NotBlank, @NotNull, @Range(min=1000, max=100000), @Max(9999)...
* Bean Validation 이란?
- 먼저 Bean Validation은 특정한 구현체가 아니라 Bean Validation 2.0(JSR-380)이라는 기술 표준이다.
- 검증 애노테이션과 여러 인터페이스의 모음이다.
- ex) JPA가 표준 기술이고 그 구현체로 하이버네이트가 있는 것과 같다.
- Bean Validation을 구현한 기술중에 일반적으로 사용하는 구현체는 하이버네이트 Validator이다.
- 이름이 하이버네이트가 붙었지만 ORM과는 관련이 없다.
> 하이버네이트 Validator 관련 링크 공식 사이트 : http://hibernate.org/validator/
> 공식 메뉴얼 : https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/html_single/
> 검증 애노테이션 모음: https://docs.jboss.org/hibernate/validator/6.2/reference/en-US/ html_single/#validator-defineconstraints-spec
5.2 Bean Validation 시작
- Bean Validation 기능을 어떻게 사용하는지 순수한 Bean Validation 사용법 부터 알아보자.
* Bean Validation 의존관계 추가
- 의존관계 추가 : Bean Validation을 사용하려면 의존관계를 추가해야 한다
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-validation'
...
}
- build.gradle에 spring-boot-starter-validation 추가
- Jakarta Bean Validation
> jakarta.validation-api : Bean Validation 인터페이스
> hibernate-validator 구현체
* Bean Validation 추가
package hello.itemservice.domain.item;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class Item {
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min=1000, max=1000000)
private Integer price;
@NotNull
@Max(9999)
private Integer quantity;
public Item() {
}
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
- hello.itemservice.domain.item.Item.java
- @NotBlank : 빈값 + 공백만 있는 경우를 허용하지 않는다.
- @NotNull : null 을 허용하지 않는다.
- @Range(min = 1000, max = 1000000) : 범위 안의 값이어야 한다.
- @Max(9999) : 최대 9999까지만 허용한다
- javax.validation 으로 시작하면 특정 구현에 관계없이 제공되는 표준 인터페이스(Max, NotBlank, NotNull...)이고, org.hibernate.validator 로 시작하면 하이버네이트 validator 구현체(Range)를 사용할 때만 제공되는 검증 기능이다.
- 실무에서 대부분 하이버네이트 validator를 사용하므로 자유롭게 사용해도 된다.
* Bean Validator 테스트 생성
package hello.itemservice.validation;
import hello.itemservice.domain.item.Item;
import org.junit.jupiter.api.Test;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;
public class BeanValidationTest {
@Test
void beanValidation () {
//검증기 생성 (스프링과 통합하면 이렇게 직접 선언할 필요가 없다.)
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();
Item item = new Item();
item.setItemName(" "); //공백
item.setPrice(0); //100 이하
item.setQuantity(10000); //9999이상
//검증 실행 (validate)
Set<ConstraintViolation<Item>> violations = validator.validate(item);
for (ConstraintViolation<Item> violation : violations) {
System.out.println("violation = " + violation);
System.out.println("violation = " + violation.getMessage());
}
/*
실행결과
violation = ConstraintViolationImpl{interpolatedMessage='9999 이하여야 합니다', propertyPath=quantity, rootBeanClass=class hello.itemservice.domain.item.Item, messageTemplate='{javax.validation.constraints.Max.message}'}
violation = 9999 이하여야 합니다
violation = ConstraintViolationImpl{interpolatedMessage='1000에서 1000000 사이여야 합니다', propertyPath=price, rootBeanClass=class hello.itemservice.domain.item.Item, messageTemplate='{org.hibernate.validator.constraints.Range.message}'}
violation = 1000에서 1000000 사이여야 합니다
violation = ConstraintViolationImpl{interpolatedMessage='공백일 수 없습니다', propertyPath=itemName, rootBeanClass=class hello.itemservice.domain.item.Item, messageTemplate='{javax.validation.constraints.NotBlank.message}'}
violation = 공백일 수 없습니다
*/
}
}
- hello.itemservice.validation.BeanValidationTest.java
- 검증 실행 : 검증 대상( item )을 직접 검증기에 넣고 그 결과를 받는다.
- Set 에는 ConstraintViolation 이라는 검증 오류가 담긴다. 따라서 결과가 비어있으면 검증 오류가 없는 것이다.
- Bean Validation를 직접 사용하는 방법을 알아보았다.
- 아마 지금까지 배웠던 스프링 MVC 검증 방법에 빈 검증기를 어떻게 적용하면 좋을지 여러가지 생각이 들 것이다.
- 스프링은 이미 개발자를 위해 빈 검증기를 스프링에 완전히 통합해두었다.
5.3 Bean Validation - 프로젝트 준비 V3
- 앞서 만든 기능을 유지하기 위해, 컨트롤러와 템플릿 파일을 복사하자.
- ValidationItemControllerV2를 복사해서 ValidationItemControllerV3 컨트롤러 생성
- /resources/templates/validation/v2/ 복사해서 v3 생성
> 복사 후 파일 내부의 url도 같이 변경해주어야한다. (Ctrl + R, Ctrl + Shift + R)
5.4 Bean Validation - 스프링 적용
- ValidationItemControllerV3 수정해서 Bean Validation 적용
package hello.itemservice.web.validation;
import hello.itemservice.domain.item.Item;
import hello.itemservice.domain.item.ItemRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Controller
@RequestMapping("/validation/v3/items")
@RequiredArgsConstructor
public class ValidationItemControllerV3 {
private final ItemRepository itemRepository;
@GetMapping
public String items(Model model) {
List<Item> items = itemRepository.findAll();
model.addAttribute("items", items);
return "validation/v3/items";
}
@GetMapping("/{itemId}")
public String item(@PathVariable long itemId, Model model) {
Item item = itemRepository.findById(itemId);
model.addAttribute("item", item);
return "validation/v3/item";
}
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "validation/v3/addForm";
}
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
// 검증에 실패하면 다시 입력 폼으로 redirect
if(bindingResult.hasErrors()){ //!errors.isEmpty() -> bindingResult.hasErrors() 로 변경
//검증 실패 시 model에 errors를 담고 입력폼이 있는 뷰 템플릿으로 보낸다.
log.info("errors = {} ", bindingResult);
return "validation/v3/addForm";
}
// 성공 로직
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
return "redirect:/validation/v3/items/{itemId}";
}
@GetMapping("/{itemId}/edit")
public String editForm(@PathVariable Long itemId, Model model) {
Item item = itemRepository.findById(itemId);
model.addAttribute("item", item);
return "validation/v3/editForm";
}
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @ModelAttribute Item item) {
itemRepository.update(itemId, item);
return "redirect:/validation/v3/items/{itemId}";
}
}
- hello/itemservice/web/validation/ValidationItemControllerV3.java
- 로직의 수정은 없고, addItemV1 ~ V5삭제, 이전 강의에서 새로 만든 ItemValidator 선언부 제거
- 그럼에도 불구하고 정상적으로 동작한다.
- @Validated 덕분. 해당 애노테이션이 붙어있으면 Spring의 Validator가 자동으로 동작한다.
* 스프링 MVC는 어떻게 Bean Validator를 사용하는지?
- 스프링 부트가 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다. 그 후 스프링 부트는 자동으로 글로벌 Validator로 등록한다. LocalValidatorFactoryBean 을 글로벌 Validator로 등록한다.
- 이 Validator는 @NotNull 같은 애노테이션을 보고 검증을 수행한다. 이렇게 글로벌 Validator가 적용되어 있기 때문에, @Valid , @Validated 만 적용하면 된다.
- 검증 오류가 발생하면, FieldError , ObjectError 를 생성해서 BindingResult 에 담아준다.
- 참고로, 직접 글로벌 Validator를 직접 등록하면 스프링 부트는 Bean Validator를 글로벌 Validator 로 등록하지 않는다. 따라서 애노테이션 기반의 빈 검증기가 동작하지 않기 때문에 글로벌 Validator는 조심해서 사용하자.
* 검증시 @Validated @Valid 둘다 사용가능하다.
- javax.validation.@Valid 를 사용하려면 build.gradle 의존관계 추가가 필요하다.
- implementation 'org.springframework.boot:spring-boot-starter-validation'
- @Validated 는 스프링 전용 검증 애노테이션이고, @Valid 는 자바 표준 검증 애노테이션이다. 둘중 아무거나 사용해도 동일하게 작동하지만, @Validated 는 내부에 groups 라는 기능을 포함하고 있다.
* 검증 순서
- 1. @ModelAttribute 각각의 필드에 타입 변환 시도
> 1.1. 성공하면 다음으로
> 1.2. 실패하면 typeMismatch 로 FieldError 추가
- 2. Validator 적용
* 바인딩에 성공한 필드만 Bean Validation 적용
- BeanValidator는 바인딩에 실패한 필드는 BeanValidation을 적용하지 않는다.
- 타입 변환에 성공해서 바인딩에 성공한 필드여야 BeanValidation 적용이 의미 있다.
- 모델 객체에 바인딩 받는 값이 정상으로 들어와야 검증도 의미가 있다.
> @ModelAttribute -> 각각의 필드 타입 변환시도 -> 변환에 성공한 필드만 BeanValidation 적용
> itemName에 "A" 입력 -> 타입 변환 성공 -> itemName 필드에 BeanValidation 적용
> price에 "A" 입력 -> "A"를 숫자 타입 변환 실패 -> typeMismatch FieldError 추가 -> price 필드는 BeanValidation 적용 X
5.5 Bean Validation - 에러 코드
- Bean Validation이 기본으로 제공하는 오류 메시지를 좀 더 자세히 변경하고 싶으면 어떻게 하면 될까?
- Bean Validation을 적용하고 bindingResult 에 등록된 검증 오류 코드를 보자.
- 오류 코드가 애노테이션 이름으로 등록된다. 마치 typeMismatch 와 유사하다.
* NotBlank 라는 오류 코드를 기반으로 MessageCodesResolver 를 통해 다양한 메시지 코드가 순서대로 생성된다
* @NotBlank
- NotBlank.item.itemName
- NotBlank.itemName
- NotBlank.java.lang.String
- NotBlank
* @Range
- Range.item.price
- Range.price
- Range.java.lang.Integer
- Range
* 메시지 등록
#Bean Validation 추가
NotBlank={0} 공백X
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}
- resources/erros.properties
- Validator에 관련된 메시지 적용
* BeanValidation 메시지 찾는 순서
- 1. 생성된 메시지 코드 순서대로 messageSource 에서 메시지 찾기
- 2. 애노테이션의 message 속성 사용 -> @NotBlank(message = "공백! {0}")
- 3. 라이브러리가 제공하는 기본 값 사용 -> 공백일 수 없습니다.
@NotBlank(message = "공백은 입력할 수 없습니다.")
private String itemName;
- 애노테이션의 message 사용 방법
'Spring 정리' 카테고리의 다른 글
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 12 (0) | 2022.07.05 |
---|---|
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 11 (0) | 2022.07.01 |
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 9 (0) | 2022.06.26 |
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 8 (0) | 2022.06.12 |
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 7 (0) | 2022.06.12 |