일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 로그인
- JPA 활용 2
- 트위터
- 컬렉션 조회 최적화
- 프로젝트 환경설정
- 기본문법
- JPQL
- Bean Validation
- 벌크 연산
- 타임리프
- 불변 객체
- 임베디드 타입
- 타임리프 문법
- jpa 활용
- 김영한
- 일론머스크
- 스프링
- 스프링 데이터 JPA
- API 개발 고급
- 페이징
- 예제 도메인 모델
- Spring Data JPA
- 스프링 mvc
- 검증 애노테이션
- 값 타입 컬렉션
- QueryDSL
- JPA
- JPA 활용2
- 스프링MVC
- 실무활용
- Today
- Total
RE-Heat 개발자 일지
스프링 MVC 2편 - [5] 검증2- Bean Validation(상편) 본문
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard
인프런 김영한님의 스프링 MVC 2편 강의를 토대로 정리한 내용입니다.
[1] Bean Validation - 소개
이전까지 검증기능을 작성하는 건 너무 길고 번거로웠다. 그래서 스프링에선 JAVA 코드 대신 애노테이션만으로 쉽게 검증할 수 있도록 돕는 API를 제공한다.
■ Bean Validation이란?
Bean Validation 2.0이라는 기술 표준으로, 검증 애노테이션과 여러 인터페이스의 모음이다.
[2] Bean Validation - 시작
■ 의존 관계 추가
추가되는 라이브러리
① jakarta.validation-api : Bean Validation 인터페이스
② hibernate-validator : 구현체
Item
@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;
}
}
▶ 검증 애노테이션
① @NotBlank : 빈값 + 공백만 있는 것을 허용하지 않는다.
② @NotNull : null을 허용하지 않는다.
③ @Range(min = 1000, max = 1000000) : 범위 안의 값이어야 한다.
④ @Max(9999) : 최대 9999까지만 허용한다.
@NotNull은 javax.validation.constrains.NotNull
@Range는 org.hibernate.validator.constraints.Range로
java.validation 은 표준 인터페이스인 반면 org.hibernate.validator는 하이버네이트 validator 구현체를 사용할 때만 제공되는 기능이다. 하지만 실무에선 대부분 하이버네이트 validator를 사용하므로 자유롭게 써도 된다.
BeanValidationTest
public class BeanValidationTest {
@Test
void beanValidation(){
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Item item = new Item();
item.setItemName(" "); //공백
item.setPrice(0);
item.setQuantity(10000);
Set<ConstraintViolation<Item>> violations = validator.validate(item);
for (ConstraintViolation<Item> violation : violations){
System.out.println("violation = " + violation);
System.out.println("violation = " + violation.getMessage());
}
}
}
▶ 검증기 생성
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
▶검증 실행
Set<ConstraintViolation<Item>> violations = validator.validate(item);
1] 검증 대상(item)을 직접 검증기에 넣고 그 결과를 받는다.
2] Set에는 ConstraintViolation이라는 검증 오류가 담기므로 결과가 비어있으면 검증 오류는 없는 것이다.
=> 스프링과 통합되지 않을 땐 검증기를 위 코드처럼 생성한다. 스프링과 통합하면 이 코드를 직접 작성하진 않는다
실행결과
검증오류 발생한 객체, 필드, 메시지 정보 등 다양한 정보를 확인할 수 있다.
참고]
@NotBlank(message = "공백 X") 이런 식으로 원하는 오류 메시지를 직접 설정할 수 있다.
[3] Bean Validation - 스프링 적용
ValidationItemControllerV3
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
//특정 필드가 아닌 복합 룰 검증
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
//검증에 실패하면 다시 입력 폼으로
if (bindingResult.hasErrors()) {
log.info("errors={}" + bindingResult);
return "validation/v3/addForm";
}
//성공 로직
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
@Validated를 넣어주는 방식으로 Bean Validator를 사용한다.
Item & 실행화면
■ 스프링 MVC는 어떻게 Bean Validator를 사용할까?
스프링 부트가 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합한다.
■ 스프링 부트는 자동으로 글로벌 Validator로 등록한다.
LocalValidatorFactoryBean을 글로벌 Validator로 등록한다. 이 Validator는 @NotNull 같은 애노테이션을 보고 검증을 수행한다. 이런 식으로 글로벌 Validator가 적용돼 있기 때문에, @Validated @valid만 적용하면 된다.
검증 오류가 발생하면 FileError, ObjectError를 생성해서 BindingResult에 담아준다.
주의] 글로벌 Validator를 직접 등록하면 스프링 부트는 Bean Validator를 글로벌 Validator로 등록하지 않는다.
■ 검증순서
① @ModelAttribute 각각의 필드에 타입 변환 시도
1] 성공하면 다음으로
2] 실패하면 typeMismatch로 FieldError 추가
② Validator 적용
바인딩에 성공한 필드만 Bean Validation 적용. 타입 변환에 실패해서 바인딩에 실패한 필드는 애초에 BeanValidation 적용이 의미가 없으므로 typeMismatch로 간다.
ex)
1] itmeName에 문자 'A' 입력 → 타입 변환 성공 → itemName 필드에 BeanValidation 적용
2] Price에 문자 'A' 입력 → 'A' 숫자 타입 변환 시도 실패 → typeMismatch Field Error 추가 → price 필드는 BeanValidation 적용 X
[4] Bean Validation - 에러 코드
Bean Validation이 기본으로 제공하는 오류 메시지를 좀 더 자세하게 변경하는 방법은?
Bean Validation도 MessageCodesResolver를 통해 다양한 메시지 코드가 순서대로 생성된다.
@NotBlank
1. code + "." + object name + "." + field => NotBlank.item.itemName
2. code + "." + field => NotBlank.itemName
3. code + "." + field type => NotBlank.java.lang.String
4. code => NotBlank
스프링 MVC 2편 검증 1 - Validation의 DefaultMessageCodesResolver 기본 메시지 생성 규칙과 같다.
그러므로
errors.properties에 자세한 메시지를 입력하면 BeanValidation의 오류 메시지도 바꿀 수 있다.
실행화면 & errors.properties
■ BeanValidation 메시지 찾는 순서
① 생성된 메시지 코드 순서대로 messageSource에서 메시지 찾기
② 애노테이션의 message 속성 이용
@NotBlank(message = "공백! {0}")
③ 라이브러리가 제공하는 기본 값 사용
[5] Bean Validation - 오브젝트 오류
필드가 아닌 오브젝트 오류는 어떻게 처리할 수 있을까?
방법 1] @ScriptAssert() 사용
실행화면 & Item
간편해 보이지만, 제약이 많고 복잡하다. 또 실무에선 검증 기능이 해당 객체의 범위를 넘어설 때도 꽤 있는데, 그럴 때 대응이 어렵다는 단점이 있다.
방법 2] 오브젝트 오류 관련 부분만 직접 자바 코드로 작성하기
//@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes, Model model) {
//특정 필드가 아닌 복합 룰 검증
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
=> Object 관련 전체 예외 코드를 다시 추가해 주는 방식
@ScriptAssert()는 제약이 많으므로 오브젝트에 한해 직접 자바 코드로 작성하는 것을 권장한다.
[6] Bean Validation - 수정에 적용
크게 달라지는 부분은 없다. 파라미터에 @Validated를 추가하고 BindingResult를 매개변수에 넣으면 된다.
아울러 editForm.html도 새 style을 적용하고 오류 메시지를 출력할 곳을 추가해 주면 된다.
ValidationItemControllerV3
@PostMapping("/{itemId}/edit")
public String edit(@PathVariable Long itemId, @Validated @ModelAttribute Item item, BindingResult bindingResult) {
//특정 필드가 아닌 복합 룰 검증
if (item.getPrice() != null && item.getQuantity() != null) {
int resultPrice = item.getPrice() * item.getQuantity();
if (resultPrice < 10000) {
bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
}
}
//검증에 실패하면 다시 입력 폼으로
if (bindingResult.hasErrors()) {
log.info("errors={}" + bindingResult);
return "validation/v3/editForm";
}
itemRepository.update(itemId, item);
return "redirect:/validation/v3/items/{itemId}";
}
'백엔드 > 스프링' 카테고리의 다른 글
스프링 MVC 2편 - [6] 로그인 처리 1 - 쿠키·세션(상편) (0) | 2023.07.20 |
---|---|
스프링 MVC 2편 - [5] 검증2- Bean Validation(하편) (0) | 2023.07.19 |
스프링 MVC 2편 - [4] 검증 1 - Validation(하편) (1) | 2023.07.16 |
스프링 MVC 2편 - [4] 검증 1 - Validation(상편) (0) | 2023.07.14 |
스프링 MVC 2편 - [3] 메시지·국제화 (0) | 2023.07.13 |