DEVELOPMENT/Spring

[스프링부트 완전정복] 9. 유효성 검사

Tiny Commit 2025. 11. 21. 15:25

 

 

 

 

 

 

1. 유효성 검사의 개요

  • 폼데이터를 전달할때 유효하지 않은 값이 전송 될 수 있다. 이를 방지하기 위해 
  • 유효성 검사는 폼 페이지의 입력 값이 서버로 전송되기전 정해진 규칙에 따라 입력되어 있는지, 값이 타장한지 검사합니다. 

1. 유효성 검사를 위한 설정

  • @Valid
  • 의존 라이브러리 등록
implementation 'org.springframework.boot:spring-boot-starter-validation'

 

 

2. 유효성 검사의 유형

  • Bean Validation: 특정 도메인 클래스의 멤버 변수 → 제약사항 애너테이션
  • ConstraintValidation: 멤버 변수에 사용자 정의 애너테이션을 정의 →  유효성 검사 수행, 클래스 제약 조건 부여
  • Validator: 스프링부트에서 제공하는 Validator 인터페이스 →  애플리케이션의 모든 계층에서 유효성 검사
  • 유효성 검사 폼데이터 
    • 입력 데이터가 null인가
    • 형식에 맞는가 (날짜, 이메일)
    • 나이를 숫자로 입력했는가
    • 입력 길이를 초과하는가
    • 로그인 인증 시 아이디와 패스워드가 유효한가
    • 회원가입 시 아이디가 중복되는가

 

 

 

 

2. Bean Validation을 이용한 유효성 검사

  • 제약사항 애너테이션 선언으로 필드에 대한 유효성 검사
  • @제약 사항 애너테이션 -> @Valid -> 뷰 페이지 오류 메시지 출력

1. 제약사항 애너테이션

public class 클래스 이름 {
    @제약사항 애너페이션( 속성, [, message = "오류 메시지 [오류 코드]"])
    private String 멤버 변수;

}

 

 

2. Bean Validation 제약 조건 어노테이션 유형

유형 설명 주요 속성
@AssertFalse 프로퍼티 값이 거짓(false) 인지 검사 -
@AssertTrue 프로퍼티 값이 참(true) 인지 검사 -
@DecimalMax 프로퍼티 값이 가질 수 있는 최대 실수 값 검사 value (값), inclusive (포함 여부)
@DecimalMin 프로퍼티 값이 가질 수 있는 최소 실수 값 검사 value (값), inclusive (포함 여부)
@Digits 프로퍼티 값이 가진 소수점 위치를 포함하는 정수 및 소수점 자리수 검사 integer (정수 자리수), fraction (소수 자리수)
@Email 이메일 형식인지 검사 -
@Future 프로퍼티 값이 미래 날짜/시간인지 검사 -
@FutureOrPresent 프로퍼티 값이 현재 또는 미래 날짜/시간인지 검사 -
@Max 프로퍼티 값이 가질 수 있는 최대 정수 값 검사 value (최대 값)
@Min 프로퍼티 값이 가질 수 있는 최소 정수 값 검사 value (최소 값)
@Negative 프로퍼티 값이 음수인지 검사 -
@NegativeOrZero 프로퍼티 값이 음수 또는 0인지 검사 -
@NotBlank 프로퍼티 값이 null이 아니고 공백이 아닌 문자를 포함하는지 검사 -
@NotEmpty 프로퍼티 값(문자열, 컬렉션 등)이 비어있지 않은지 검사 -
@NotNull 프로퍼티 값이 null이 아닌지 검사 -
@Null 프로퍼티 값이 Null인지 검사 -
@Past 프로퍼티 값이 과거 날짜/시간인지 검사 -
@PastOrPresent 프로퍼티 값이 과거 또는 현재 날짜/시간인지 검사 -
@Pattern 프로퍼티 값이 정의된 정규 표현식에 일치하는지 검사 regexp (정규 표현식)
@Positive 프로퍼티 값이 양수인지 검사 (0 제외) -
@PositiveOrZero 프로퍼티 값이 양수 또는 0인지 검사 -
@Size 프로퍼티 값이 가질 수 있는 최대/최소 크기 검사 (문자열 길이, 컬렉션 크기 등) min (최소 값), max (최대 값)

 

 

 

 

3. 제약사항 어노테이션의 기본 메시지

  • 제약사항을 위반하면 hibernate-validator-xxx.jar에서 제공하는 메시지 출력
  • 어노테이션들이 message 속성을 지정하지 않았을 때 기본으로 출력하는 오류 메시를 보여줍니다.
유형 기본 메시지
@AssertFalse false여야 합니다.
@AssertTrue true여야 합니다.
@DecimalMax 다음 값 이하여야 합니다({value}).
@DecimalMin 다음 값 이상이어야 합니다({value}).
@Digits 숫자 값이 한계를 초과합니다({fraction}자리)
@Email 올바른 형식의 이메일 주소여야 합니다.
@Future 미래 날짜여야 합니다.
@FutureOrPresent 현재 또는 미래 날짜여야 합니다.
@Max ${value} 이하여야 합니다.
@Min ${value} 이상이어야 합니다.
@Negative 0 미만이어야 합니다.
@NegativeOrZero 0 이하이어야 합니다.
@NotBlank 공백일 수 없습니다.
@NotEmpty 비어 있을 수 없습니다.
@NotNull null이어서는 안됩니다.
@Null null이어야 합니다.
@Past 과거 날짜여야 합니다.
@PastOrPresent 과거 또는 현재 날짜여야 합니다.
@Pattern ${regexp}와 일치해야 합니다.
@Positive 0보다 커야 합니다.
@PositiveOrZero 0 이상이어야 합니다.
@Size 크기가 ${min}에서 ${max} 사이여야 합니다.
 
public class 클래스 이름 {
    @NotEmpty
    @Size(min=4, max=10)
	@Min(value=0)
}
    @PostMapping
    public String submitForm( @Valid @ModelAttribute("product") Product product, // @Valid로 검사 실행
            BindingResult bindingResult) {                     // 검사 결과를 받음

        if(bindingResult.hasErrors()) {
            // 유효성 검사 실패 시, 다시 폼 페이지를 호출 (에러 메시지는 Thymeleaf가 처리)
            return "viewPage01";
        }
        
        // 유효성 검사 성공 시, 결과 페이지 호출
        return "viewPage01_result"; 
    }
}

 

< !--viewPage01.html-->
<body>
	<h3>유효성 검사</h3>
	
	<form th:object="${product}" action="/exam01"  method="post">
		<p>품명 : <input type="text"   th:field="*{name}"> <span th:errors="*{name}"></span>
		<p>가격 : <input type="text"   th:field="*{price}"> <span th:errors="*{price}"></span>
		<p><input type="submit" value="확인"/> 
		<input type="reset"  value="취소"/>
	</form>
</body>


//viewPage_result01.html
<body>
	<h3>유효성 검사</h3>
	<p>품명 :  [[${product.name}]]
		<p>가격 :  [[${product.price}]]
		
</body>
< !--viewPage01_result.html-->
<body>
	<h3>유효성 검사</h3>
	<p>품명 :  [[${product.name}]]
	<p>가격 :  [[${product.price}]]
		
</body>

 

 

 

 

4. 제약사항 애너테이션에 사용자 정의 오류 메시지 사용하기

  • 메시지 속성 값에 출력할 오류 메시지를 직접 정의한다.
    • 오류코드 = 출력할 오류 메시지
# 제약사항 어노테이션.필드이름 = 출력할 오류 메시지 형식 사용
NotEmpty.product.name = 값을 입력해 주세요
Min.product.price = 0 이상의 값을 입력해 주세요


# 메시지 리소스 파일의 기본 이름 설정 (경로는 src/main/resources가 기본)
spring.messages.basename = messages
# 메시지 리소스 파일의 인코딩을 UTF-8로 설정 (한글 깨짐 방지)
spring.messages.encoding = UTF-8

 

// Product.java (메시지 리소스 파일 사용 시)
public class Product {
    
    // message를 생략하면 Spring이 messages.properties에서 키를 찾아 출력
    @NotEmpty 
    @Size(min=4, max=10, message="4자~10자 이내로 입력해 주세요") 
    private String name;

    @Min(value=0) // messages.properties의 Min.product.price 키와 매핑됨
    private int price;
}

 

 

-> 도매인 클래스의 프로퍼티 값에 대한 중복 여부를 체크할 수 없다.

( 독립적인 라이브러리이기 때문에, 기본적으로 DB나 외부 시스템에 접근하는 로직을 포함하고 있지 않습니다: 아이디 중복 검사 x)

 

 

 

 

3. ConstraintValidator 인터페이스를 이용한 유효성 검사

1. 사용자 정의 애너체이션 생성

  • 사용자 정의 애너테이션을 이용해 유효성 검사
  • 사용자 정의 애너테이션 선언, 유효성 검사, 오류 메시지
@Target({ElementType.FIELD}) // 1. 애너테이션을 적용할 위치 (필드)
@Retention(RetentionPolicy.RUNTIME) // 2. 런타임까지 유지
@Constraint(validatedBy = {CustomValidator.class}) // 3. 검증 로직을 구현할 클래스 지정
public @interface CustomConstraint {

    // 4. 필수 요소: 오류 메시지 설정
    String message() default "기본 사용자 정의 오류 메시지입니다.";

    // 5. 필수 요소: 유효성 검사 그룹 지정 (기본값)
    Class<?>[] groups() default {};

    // 6. 필수 요소: 페이로드 지정 (기본값)
    Class<? extends Payload>[] payload() default {};
    
    // 7. (선택적) 사용자 정의 속성 추가
    int minLength() default 5;
}
  • @Constraint : 제약사항을 생성하는 애너테이션
  • @Retention : 지속 시간 결정
  • @Target : 적용 위치 결정

 

 

 

2. 구현체 생성

  • ConstraintValidator 구현체 -> 유효성 검사 클래스 
  • initialize() -> 사용자 정의 애너테이션 정보 초기화
  • isValid() -> 유효성 검사 로직 수행
// domain

// ConstraintValidator<애너테이션, 검사 대상 타입>
public class CustomValidator implements ConstraintValidator<CustomConstraint, String> {
    
    private int requiredMinLength;

    // 1. 초기화 메서드
    @Override
    public void initialize(CustomConstraint constraintAnnotation) {
        // 정의한 애너테이션의 속성값(여기선 minLength)을 가져와 초기화합니다.
        this.requiredMinLength = constraintAnnotation.minLength();
    }

    // 2. 검증 로직 메서드
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // null 값은 @NotNull로 처리하는 것이 일반적이므로 여기서는 true 반환
        if (value == null) {
            return true;
        }
        
        // 실제 유효성 검사 로직
        // 입력된 문자열의 길이가 애너테이션에서 설정한 minLength보다 크거나 같으면 true 반환
        return value.length() >= requiredMinLength;
    }
}
public class UserDto {
    
    // @CustomConstraint를 사용
    @CustomConstraint(minLength = 8, message = "아이디는 최소 8자 이상이어야 합니다.")
    private String userId;

}

 

 

 

 

 

4. Validator 인터페이스를 이용한 유효성 검사

  • Valudator인터페이스 구현체생성 -> @InitBinder메서드 추가 -> @Valid를 이용한 유효성 검사 -> 뷰페이지 오류 메시지 출력

1. 구현체 생성

  • Validator의 인터페이스 메서드
    • supports(Class<?> clazz): 지원 여부 확인: 
      • 객체를 유효성 검사할 수 있는지 여부를 반환합니다.
      • (Product.class.isAssignableFrom(clazz) 등)
    • validate(Object target, Errors errors):  실제 검증 로직: 유효성 검사를 수행.
      • target은 검사 대상 객체(DTO/Entity)이며,
      • errors는 검사 실패 시 오류 정보(에러 코드, 메시지 등)를 담는 객체입니다.
  • ValidationUtils의 메서드
    • rejectIfEmpty(Errors errors, String field, String errorCode):
      • 빈 값 검사:
      • 지정된 field의 값이 null이거나 빈 문자열 ("")인 경우, errors 객체에 errorCode로 오류를 등록합니다.
    • rejectIfEmptyOrWhitespace(Errors errors, String field, String errorCode): 빈 값/공백 검사지정된 field의 값이 null, 빈 문자열, 또는 공백 문자(" ")로만 구성되어 있는 경우, errors 객체에 errorCode로 오류를 등록합니다. (가장 흔하게 사용)
    • invokeValidator(Validator validator, Object target, Errors errors):
      • 검증기 호출:
      • 다른 Validator 인스턴스를 호출하여 특정 객체에 대한 유효성 검사를 위임하는 데 사용됩니다.
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.springboot.domain.Product;

public class ProductValidator implements Validator {

    // 1-1. supports(): 이 검증기가 어떤 클래스를 지원하는지 정의
    @Override
    public boolean supports(Class<?> clazz) {
        // Product 클래스 타입인지 확인
        return Product.class.isAssignableFrom(clazz);
    }

    // 1-2. validate(): 실제 유효성 검사 로직 구현
    @Override
    public void validate(Object target, Errors errors) {
        Product product = (Product) target;

        // **(1) ValidationUtils를 이용한 빈 값(NotBlank) 검사**
        // "name" 필드가 비어있으면 "notBlank" 에러 코드를 Errors 객체에 추가
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "notBlank");

        // **(2) 직접 코드를 이용한 특정 조건(Min/Max) 검사**
        if (product.getPrice() < 0 || product.getPrice() > 100000) {
            // "price" 필드가 조건에 맞지 않으면 "priceRange" 에러 코드를 Errors 객체에 추가
            errors.rejectValue("price", "priceRange");
        }
    }
}

 

 

 

 

2. @initBinder를 선언한 메서드 추가

  • @initBinder가 선언된 메서드에 Validator을 미리 등록하면 해당 컨트롤러의 모든 메서드에서 유효성 검사를 할 수 있음.
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.stereotype.Controller;
// ... (다른 import 생략)

@Controller
// @RequestMapping("/exam01")
public class Example01Controller {
    
    // 2. @InitBinder 메서드: 요청 처리 전에 실행되며 검증기를 WebDataBinder에 등록
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        // ProductValidator의 인스턴스를 생성하여 WebDataBinder에 추가
        binder.addValidators(new ProductValidator());
    }

    // ... (GetMapping, PostMapping 메서드)
}

 

 

 

 

 

 

 

5. [도서 쇼핑몰] 도서 등록 데이터의 유효성 검사하기

1. Bean Validation으로 유효성 검사하기

  • 의존 라이브러리 등록
  • 메시지 리소스 파일 작성 (message.properties)
  • 도서 클래스에 제약사항 애너테이션 설정 (Book.java)
  • 도서 등록 데이터의 우효성 검사 실행 (BookController)
  • 도서 등록 페이지 수정 (addBook.html)

2. ConstraintValidator 인터페이스 유효성 검사하기

  • 메시지 리소스 파일 작성
  • 도서 클래스에 자용사 정의 애너테이션 설정 (Book.java)
  • 사용자 정의 애너페이션 생성 (BookId.java)
  • 도서 아디이의 유효성 검사 클래스 생성 (VookVAlidator.java)

3. Validator 인터헤이스로 유효성 검사하기

  • 메시지 리소스 파일 설정
  • 제약사항의 유효성 검사 클래스 생성 (UnitslnStockValidator.java)
  • 도서 재고수의 유효성 검사 실행 (BookController.java)
  • 도서 등록 페이지 수정 (addBook.html)

4. 제약사항 애너테이션과 VAlidator 인터페이스를 연동한 유효성 검사하기

  • 유효성 검사클래스 생성 (BookValidator.java)
  • 오류 메시지 가져오기 (Book.java)
  • 유효성 검사 실행 (BookController.java)
  • 유효성 검사 연동 (ValidationConfig.java)

 

 

 

 

 

 

 


 


연습문제

 

 


 


출처 : 송미영, 『 스프링부트 완전정복: 개념부터 실정 프로젝트까지 』길벗캠퍼스 (2024).