Java에서는 null 값에 대해서 접근 하려고 할 때 null pointer exception이 발생 함으로, 이러한 부분을 방지 하지 위해서 미리 검증을 하는 과정

  1. 검증해야 할 값이 많은 경우 코드의 길이가 길어 진다
  2. 구현에 따라서 달라 질 수 있지만 Service Logic과의 분리가 필요
  3. 흩어져 있는 경우 어디에서 검증을 하는지 알기 어려우며, 재사용의 한계가 있음
  4. 구현에 따라 달라 질 수 있지만, 검증 Logic이 변경 되는 경우 테스트 코드 등 참조하는 클래스에서 Logic이 변경되어야 하는 부분이 발생 할 수 있음

 

어노테이션 의미 기타
@Size 문자 길이 측정 Int Type 불가
@NotNull null 불가  
@NotEmpty null, “” 불가  
@NotBlank null, “”,” “불가  
@Past 과거 날짜  
@PastOrPresent 오늘이거나 과거 날짜  
@Future 미래 날짜  
@FutureOrPresent 오늘이거나 미래 날짜  
@Pattern 정규식 적용  
@Max 최대값  
@Min 최소값  
@AssertTrue / False 별도 Logic 적용  
@Valid 해당 object valication 실행  

 

 

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-validation' // 추가
}

Gradle 디펜던시에 validation 추가

 

 

 

package com.example.validation.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;

public class User {

    @NotBlank
    private String name;

    @Min(value = 0)
    private int age;

    @Email
    private String email;

    @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "핸드폰 번호의 양식과 맞지 않습니다. 01x-xxx(x)-xxxx")
    private String phoneNumber;

    /* 이하 생략  */ 
}

@Valid 을 사용한 객체는 안에 Validation과 관련된 어노테이션이 붙은 멤버 변수(속성) 값을 검증한다.

각 어노테이션은 message 값 설정을 통해 에러 발생 시 원하는 메세지로 수정 가능하다.

 

package com.example.validation.controller;

import com.example.validation.dto.User;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class ApiController {

    @PostMapping("/user")
    public ResponseEntity user(@Valid @RequestBody User user, BindingResult bindingResult) {

        if(bindingResult.hasErrors()) {
            StringBuilder sb = new StringBuilder();
            bindingResult.getAllErrors().forEach(objectError -> {
                FieldError field = (FieldError) objectError;
                String message = field.getDefaultMessage();

                System.out.println("field : " + field.getField() + ", message : " + message);

                sb.append("field : ").append(field.getField()).append(", message : ").append(message);
            });

            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(sb.toString());
        }

        // logic

        return ResponseEntity.ok(user);
    }

}

컨트롤러 단에서 검증 시에는 BindingResult 객체를 활용하여 에러 발생 및 메시지 확인이 가능하다.

 

 Spring Boot Custom Validation

 

1. AssertTrue / False 와 같은 method 지정을 통해서 Custom Logic 적용 가능
    메소드명을 is*로 시작해야 한다.

 

package com.example.validation.dto;

import jakarta.validation.constraints.*;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class User {

    @NotBlank
    private String name;

    @Min(value = 0)
    private int age;

    @Email
    private String email;

    @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "핸드폰 번호의 양식과 맞지 않습니다. 01x-xxx(x)-xxxx")
    private String phoneNumber;

    @Size(min = 6, max = 6)
    private String reqYearMonth; // yyyyMM


     /* 중간 생략 */

    public String getReqYearMonth() {
        return reqYearMonth;
    }

    public void setReqYearMonth(String reqYearMonth) {
        this.reqYearMonth = reqYearMonth;
    }

    @AssertTrue(message = "yyyyMM의 형식에 맞지 않습니다.")
    public boolean isReqYearMonthValidation() {

        try {
            LocalDate localDate = LocalDate.parse(getReqYearMonth()+"01", DateTimeFormatter.ofPattern("yyyyMMdd"));
        } catch (Exception e) {
            return false;
        }

        return true;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", phoneNumber='" + phoneNumber + '\'' +
                ", reqYearMonth='" + reqYearMonth + '\'' +
                '}';
    }
}

 

2. ConstraintValidator 를 적용하여 재사용이 가능한 Custom Logic 적용 가능
    - message: 유효성 검사 실패 시 알릴 메시지 설정
    - groups: 유효성 검사를 상황별로 분리하여 적용할 때 사용.
    -payload: 검증 실패 시 추가 정보를 전달하거나 메타데이터를 활용할 때 사용.
    -pattern (커스텀):  YearMonthValidator에서 검증 시 사용할 패턴 설정

 

package com.example.validation.annotation;

import com.example.validation.validator.YearMonthValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Constraint(validatedBy = {YearMonthValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface YearMonth {
    String message() default "yyyyMM 형식에 맞지 않습니다.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String pattern() default "yyyyMMdd";

}

@Constraint 어떤 Custom Validator 클래스로 검증을 할 것 인지 설정한다.  Custom Validator 클래스는 ConstraintValidator 인터페이스를 구현한 클래스로 정의할 수 있다.
ConstraintValidator<어노테이션명, 타입명>

 

package com.example.validation.validator;

import com.example.validation.annotation.YearMonth;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class YearMonthValidator implements ConstraintValidator<YearMonth, String> {

    private  String pattern;

    @Override
    public void initialize(YearMonth constraintAnnotation) {
        this.pattern = constraintAnnotation.pattern();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {

        try {
            LocalDate localDate = LocalDate.parse(value+"01", DateTimeFormatter.ofPattern(this.pattern));
        } catch (Exception e) {
            return false;
        }
        return true;
    }
}

initialize에서 커스텀 어노테이션 사용 시 설정한 값을 가져온다. 그 후에 isValid 메소드에서 검증 로직을 추가한다.

 

 

package com.example.validation.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;

import java.util.List;

public class User {

    @NotBlank
    private String name;

    @Max(value = 90)
    private int age;

    @Valid
    private List<Car> cars;


	  /* 생략 */
}

Validation를 적용한 객체 안에 또 다른 객체가 있을 경우에 또 다른 객체 또한 Validator이 필요한 경우에는 반드시 @Valid 어노테이션을 추가해야한다.

'Spring' 카테고리의 다른 글

Filter  (1) 2025.04.15
Spring Boot Exception 처리  (0) 2025.03.24
Java Bean 규약  (0) 2025.03.24
AOP 관점지향 프로그램  (0) 2025.03.23
IoC와 DI  (0) 2025.03.19

+ Recent posts