스프링 부트 - 검증2: Bean Validation

SeungTaek·2021년 8월 25일
0
post-thumbnail

본 게시물은 스스로의 공부를 위한 글입니다.
틀린 내용이 있을 수 있습니다.

📒 Bean Validation이란

  • 스프링에서 유효성 검증 로직을 구현하기 위한 사실상 표준

  • 검사 대상 클래스에 어노테이션 기반 제약 조건을 선언하여 간결하게 유효성 검사가 가능하다.

  • Bean Validation을 사용하려면 validation 의존 관계를 추가해야 한다.

implementation 'org.springframework.boot:spring-boot-starter-validation'

📒 검증 애노테이션

  • @NotBlank : 빈값 + 공백만 있는 경우를 허용하지 않는다.
  • @NotNull : null 을 허용하지 않는다.
  • @Range(min = 1000, max = 1000000) : 범위 안의 값이어야 한다.
  • @Max(9999) : 최대 9999까지만 허용한다.
  • @Min(10) : 최소 10까지만 허용한다.
  • @Size(min=0, max=10) : 문자열 길이, 컬렉션 사이즈의 제한 범위를 지정
  • @AssertTrue : 값이 true인 것을 검증
  • @AssertFalse : 값이 false인 것을 검증
  • @Pattern(regexp="[a-zA-z0-9]*") : 지정한 정규 표현과 일치하는 것을 검증(예시는 영숫자 검증)
  • @DecimalMax("100.0") : 소수점 이하를 포함해서 검증할 때는 Max가 아닌, DecimalMax 사용
  • @DecimalMin("10.0") : 소수점 이하를 포함해서 검증할 때는 Min가 아닌, DecimalMin 사용
  • @Digits(integer=1, fraction=3) : 정수부와 소수부의 자릿수 검증. String 타입도 검증할 수 있다.
  • @Future : 미래 날짜인 것을 검증 (Date 타입 등)
  • @Past : 과거 날짜인 것을 검증 (Date 타입 등)
  • @Valid : 중첨된 Form을 검증 (클래스 타입)
  • @Length(min-0, max=5) : 문자열 길이 지정(문자열 전용 Size)
  • @Email : 문자열이 이메일 주소 형식인지 검증
  • @CreditCardNumber : 문자열이 신용카드 번호 형식인지 검증
  • @URL : 문자열이 URL 형식인지 검증

검증 애노테이션 모음

cf) @NotNull, @NotEmpty, @NotBlank 의 차이점

  • @NotNull: null만 허용하지 않는다. 공백문자("")나 스페이스, 탭은 허용한다.
  • @NotEmpty: null 과 공백문자("")를 둘 다 허용하지 않는다. 스페이스, 탭은 허용한다.
  • @NotBlank: null 과 공백문자(""), 스페이스, 탭을 모두 허용하지 않는다.
  • @NotEmpty@NotBlank는 컬렉션, 문자열, 배열용이다. 정수(Integer)에 사용하면 에러 발생한다. 따라서 Integer 타입에는 @NotNull을 사용할 것

🎈사용예시

@Data
public class ItemSaveForm {
	@NotBlank
 	private String itemName;
    
 	@NotNull @Range(min = 1000, max = 1000000)
 	private Integer price;
    
 	@NotNull @Max(value = 9999)
 	private Integer quantity;
}

📒 검증 순서

  1. @ModelAttribute 각각의 필드에 타입 변환 시도

    • 성공하면 다음으로

    • 실패하면 errorCode=typeMismatchFieldError 추가

  2. Validator 적용

    • 즉, 바인딩에 성공한 필드만 Bean Validation을 적용한다.

📒 에러 코드 자동 생성

  • MessageCodesResolver를 통해 메시지 코드가 순서대로 생성된다.
  1. 애노테이션.오브젝트.필드

  2. 애노테이션.필드

  3. 애노테이션.타입

  4. 애노테이션

ex

  • @NotBlank

    • NotBlank.item.itemName
    • NotBlank.itemName
    • NotBlank.java.lang.String
    • NotBlank
  • @Range

    • Range.item.price
    • Range.price
    • Range.java.lang.Integer
    • Range

🎈 메시지 등록

#errors.properties 추가
NotBlank={0} 공백X 
Range={0}, {2} ~ {1} 허용
Max={0}, 최대 {1}
  • {0} 은 필드명이고, {1} , {2} ...은 각 애노테이션 마다 다르다

🎈 BeanValidation이 오류 메시지를 찾는 우선순위

1. 생성된 메시지 코드 순서대로 `messageSource` 에서 메시지 찾기

2. 애노테이션의 message 속성 사용 `@Range(min=1, max=10, message = "{min}~{max} 범위의 숫자를 입력하세요")`

3. 라이브러리가 제공하는 기본 값 사용

📒 오브젝트 오류

  • @ScriptAssert() 를 사용하면 된다.
  • 그런데 실제 사용해보면 제약이 많고 복잡하다. 그리고 실무에서는 검증 기능이 해당 객체의 범위를 넘어서는 경우들도 종종 등장하는데, 그런 경우 대응이 어렵다.
  • 따라서 @ScriptAssert() 보다는 자바 코드에서 bindingResult.reject를 사용하는것을 추천한다.
@PostMapping("/add")
public String addItem(@Validated @ModelAttribute Item item, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
	//특정 필드 예외가 아닌 전체 예외(Global Error)
    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 "/add";
	 }
    
 	//성공 로직
 	Item savedItem = itemRepository.save(item);
 	redirectAttributes.addAttribute("itemId", savedItem.getId());
 	redirectAttributes.addAttribute("status", true);
	return "redirect:/items/{itemId}";
}

📒 Form 전송 객체 분리

  • 등록과 수정이 같은 도메인을 사용하지만, 비지니스 요구사항이 다를때가 있다.

  • 예를 들면 다음과 같은 경우이다.

    • 등록할 때에는 price가 1000 이상되어야 하지만, 수정할 때에는 price가 100 이상되어야 한다.
    • 등록할 때에는 이름이 주민번호가 필요하지만, 수정할 때에는 필요하지 않다.
  • 공통 도메인을 직접 사용하지 않고, ItemSaveForm, ItemUpdateForm 같은 폼 전송을 위한 별도의 모델 객체를 만들어서 사용한다.

  • 예를 들면 다음과 같은 로직을 돈다.

    1. HTML Form에서 요청
    2. Controller에서 @ModelAttribute ItemSaveForm로 입력값 받기
    3. 도메인 Item 생성 및 세팅
    4. RepositoryItem 저장

인프런의 '스프링 MVC 2편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요

profile
I Think So!

0개의 댓글