Bean Validation 1

사나이장대산·2024년 11월 14일

Spring

목록 보기
23/26

Bean Validation

특정 필드 검증의 경우 빈값, 길이, 크기, 형식 과 같은 간단한 로직이다. 이러한 로직들을 모든 프로젝트에 적용할 수 있도록 표준화 한 것이 Bean Validation이다.

  • Bean Validation 등장배경
@PostMapping("/v3/member")
public String createMemberV3(
        // 1. @ModelAttribute 뒤에 2. BindingResult가 위치한다.
        @ModelAttribute MemberCreateRequestDto request,
        BindingResult bindingResult,
        Model model
) {

    System.out.println("/V3/member API가 호출되었습니다.");

    // 3. Validation
    if (request.getAge() == null || request.getAge() < 0) {
        // BindingResult FieldError 추가
        bindingResult.addError(
                new FieldError("request", "age", "age 필드는 필수이며 0 이상의 값이어야 합니다.")
        );
    }

    // error 처리
    if (bindingResult.hasErrors()) {
        System.out.println("Error를 처리하는 로직");
        // error 페이지 반환
        return "error";
    }

    // Model에 저장
    model.addAttribute("point", request.getPoint());
    model.addAttribute("name", request.getName());
    model.addAttribute("age", request.getAge());

    return "complete";
}
  • 실행결과
  • 검증 기능을 매번 BingdingResult 예시처럼 코드로 구현하는것은 번거로운 일이다.
  • 이 경우 Controller의 크기가 너무 커지며 단일 책임 원칙에 위배된다.
  • 개발자들은 불편함을 참지 않는다.
  • Bean Validation
    • 객체의 필드나 메서드에 제약 조건을 설정하여, 올바른 값을 가지고 있는지 검증하는 표준화된 방법
    1. Bean Validation은 기술 표준 인터페이스이다.
    2. 다양한 Annotation들과 여러가지 Interface로 구성되어 있다.
      • Bean Validation(인터페이스) 구현체인 Hibernate Validator를 사용한다.

Bean Validation 적용 예시

@Getter
public class SignUpRequestDto {
	
	@NotBlank
	private String name;

	@NotNull
	@Range(min = 1, max = 120)
	private Integer age;

}
@Controller
public class BeanValidationController {

		@PostMapping("/model-attribute")
    public String beanValidationV1(
            @Validated @ModelAttribute SignUpRequestDto dto
    ) {
        // 로직
        
        // ViewName
        return "complete";
    }

}

@RestController
public class BeanValidationRestController {

		@PostMapping("/request-body")
    public String beanValidationV2(
            @Validated @RequestBody SignUpRequestDto dto
    ) {
        // 로직
        
				// 문자 Data 반환
        return "회원가입 완료";
    }

}
  • Annotation을 적용시키는것 만으로 Validation을 아주 쉽게 적용할 수 있다.
  • Controller에 개발자가 기본적인 검증 로직을 작성할 필요가 없어졌다.
  • RestController의 @RequestBody에도 사용할 수 있다.

Field Error

특정 필드 검증의 경우 빈값, 길이, 크기, 형식 과 같은 간단한 로직이다. 이러한 로직들을 모든 프로젝트에 적용할 수 있도록 표준화 한 것이 Bean Validation이다.

  • Bean Validation 적용
    • 코드예시
      • 의존성 추가(build.gradle)

        implementation 'org.springframework.boot:spring-boot-starter-validation'
      • implementation 뒤에 version이 따로 적혀있지 않은 이유 → Spring Boot 기능

      • External Libraries

        • 프로젝트에 적용된 라이브러리 목록

  • 파일경로 jakarta.validation-api-${version}.jar
    - jakarta.validation.constraints
  • 사용할 수 있는 다양한 Annotation들을 확인할 수 있다.
  • 파일경로 org.hibernate.validator:hibernate-validator:${version}
    • 위 Annotation 들이 동작하게 만들어주는 Validator

  1. jakarta.validation-api : Interface
  2. org.hibernate.validator:hibernate-validator : 구현체
  • 실제코드 import

  • @Range Annotation은 Hibernate Validator 에서만 동작하는것

  • @NotBlank, @NotNull 은 validation 표준 인터페이스

  • 사용된 Annotation 정리

    • @NotBlank
      1. null을 허용하지 않는다.
      2. 공백(” “)을 허용하지 않는다. 하나 이상의 문자를 포함해야한다.
      3. 빈값(””)을 허용하지 않는다.
      4. CharSequence 타입 허용
        • String은 CharSequence(Interface)의 구현체이다.
    • @NotNull
      1. null을 허용하지 않는다.
      2. 모든 타입을 허용한다.
    • @NotEmpty
      1. null을 허용하지 않는다.
      2. 빈값(””)을 허용하지 않는다.
      3. CharSequence, Collection, Map, Array 허용
@Data
public class TestDto {
	// 테스트 하고싶은 Annotation으로 변경 가능
	@NotBlank
	private String stringField;
	
	@NotNull
	@Range(min = 1, max = 9999)
	private Integer integerField;
	

}

import static org.assertj.core.api.Assertions.assertThat;

public class BeanValidationTest {
	
	@Test
	void beanValidation() {
		
		// Spring과 통합하면 아래 두줄의 코드는 사용하지 않는다.
		ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
		Validator validator = factory.getValidator();
		
		// Test 하고싶은 상황을 만들어서 검증 가능
		TestDto dto = new TestDto();
		dto.setStringField(" ");
		dto.setIntegerField(1);
		
		// DTO를 검증
		Set<ConstraintViolation<TestDto>> violations = validator.validate(dto);

		// 검증 결과가 예상대로 발생했는지 확인
		// 검증에 걸린 필드가 있어야 함
    assertThat(violations).isNotEmpty();
    // 2개의 제약 위반 발생
    assertThat(violations.size()).isEqualTo(2);
		
		// Validation에 걸린 내역을 출력
		for(ConstraintViolation<TestDto> violation : violations) {
			// 아래의 결과에 Message가 있으면 Validation에 걸린것.
			// Default Message가 있기 때문에 출력됨
			// Message를 수정하고싶다면 Annotation 속성값(message="입력")으로 설정할 수 있다.
			// 결과가 비어있으면 Validation에 걸리지 않은것.
			System.out.println("violation = " + violation.getMessage());
		}
		
	}

}

출력결과

  • 테스트 통과
  • Default Message가 출력된다.

import가 org.hibernate.validator로 시작하면 하이버네이트를 사용할 때만 제공되는 검증 기능으로 다른 구현체로 validator를 교체하였을 경우 동작하지 않는다. 하지만 org.hibernate.validator를 대부분 사용한다.

Validator

단순히 Annotation을 선언해주면 검증이 완료되는 이유는 Validator(Validation을 사용하는것)가 존재하기 때문이다. Spring Boot는 validation 라이브러리를 설정하면 'org.springframework.boot:spring-boot-starter-validation'자동으로 Bean Validator를 Spring에 통합되도록 설정해준다.

  • 동작 순서
    • Spring Boot Application 실행 시 자동으로 Bean Validator가 통합된다.
    1. LocalValidatorFactoryBean 을 Global Validator로 등록한다.
      • Class Diagram

주의사항 : Global Validator를 수동으로 등록하면LocalValidatorFactoryBean 를 등록하지 않는다.

  1. Global Validator가 Default로 적용되어 있으니 @Valid, @Validated 만 적용하면 된다.
    1. Bean Validation Annotation이 있으면 검증을 수행한다.

      ex) @NotNull, @NotBlank, @Max 등등..

    2. Validation Error가 발생하면 FieldError, ObjectError를 생성하여 BindingResult에 담아준다.

  • @Valid, @Validated 차이점

    1. @Valid 는 JAVA 표준이고 @Validated 는 Spring 에서 제공하는 Annotation이다.
    2. @Validated 를 통해 Group Validation 혹은 Controller 이외 계층에서 Validation이 가능하다.
    3. @ValidMethodArgumentNotValidException 예외를 발생시킨다.
    4. @ValidatedConstraintViolationException 예외를 발생시킨다.
  • Validator 적용

    • Validator 적용 전
      • @ModelAttribute 각각의 필드 타입에 맞추어 바인딩(변환) 시도
        • 성공 : Controller 정상 호출
        • 실패 : TypeMismatch FieldError 발생
    • Validator 적용 후
      • @ModelAttribute → 각 필드 바인딩 → 성공한 필드만 Bean Validation 적용
        • Integer 타입 필드에 문자가 오면 애초에 검증의 의미가 없다.
        • 성공 : String 필드에 문자입력 → 바인딩 성공 → String 필드에 Bean Validation 적용
        • 실패 : Integer 필드에 문자입력 → 바인딩 실패 → bindingResultTypeMismatch FieldError 추가 → 바인딩에 실패한 필드는 값이 없음(null) → Bean Validation 적용하지 않음

    Bean Validator는 바인딩에 실패한 필드는 Bean Validation을 적용하지 않는다. 바인딩(변환)에 성공한 필드만이 Bean Validation을 적용하는 의미가 있다.

profile
사나이 張大山 포기란 없다.

0개의 댓글