본문 바로가기

Spring

[Spring] Validation

Validation이란?

시스템이 미리 정의한 사양(specification)에 부합하고 있는지 검증하는 것.

즉, 입력된 값이 올바른지 확인하는 과정을 말한다.

 

Validation의 역할

  1. 검증을 통해 적절한 메시지를 유저에게 보여주어야 한다.
  2. 검증 오류로 인해 정상적인 동작을 하지 못하는 경우는 없어야 한다.
  3. 사용자가 입력한 데이터는 유지된 상태여야 한다.

 

Validation을 사용하는 이유

  1. 사용자의 실수로 잘못된 값이 들어올 수 있어서
  2. 입력값이 이상하면 서버까지 망가질 수 있어서
  3. 보안상 위험을 막기 위해서
  4. 시스템이 안정적으로 동작하려면 정확한 값이 필요해서

Validation의 종류

  설명 장점 단점 예시
프론트 검증 사용자 입력값을 화면에서 즉시 확인 사용자 경험(UX) 향상 사용자가 우회 가능
보안 취약
비밀번호에 특수문자 포함 여부 확인
서버
검증
API 호출 시 백엔드에서 검증 보안 강화논리 검증 가능 UX 저하
(요청 후 오류 알림)
회원가입 시 중복 이메일 확인
DB
검증
DB 레벨에서 제약조건으로 검증 (NOT NULL, DEFAULT, UNIQUE 등) 데이터 무결성 보장최종 방어선 오류 발생 시 복구 어려움
UX 관여 불가
이메일 칼럼에 NOT NULL 설정

 

Spring Validation 적용을 위한 준비

build.gradle에 아래 의존성을 추가해야 한다.

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

 

 

Bean Validation 애너테이션

Validation은 Annotation을 적용시키는 것 만으로 아주 쉽게 적용할 수 있다. 이를 통해 Controller에 개발자가 기본적인 검증 로직을 작성할 필요가 없어졌다.

주로 사용하는 애너테이션 설명 자주 쓰이는 곳
@NotBlank null, 빈 문자열, 공백 전부 허용 안 함 String (아이디, 이름 등)
@Size(min, max) 문자열/컬렉션 길이 제한 비밀번호, 닉네임 등
@Email 이메일 형식인지 검사 이메일
@Pattern 정규식으로 형식 검사 전화번호, 코드 등
@Min / @Max 숫자 최소값 / 최대값 지정 나이, 수량 등
@Positive 양수만 허용 금액, 수량 등
@NotNull null만 안 됨 (빈 문자열은 허용됨) 숫자, Boolean, 날짜 등
public class UserDto {

    @NotBlank(message = "이름은 필수 입력입니다.")
    private String name;

    @Size(min = 6, max = 20, message = "비밀번호는 6자 이상 20자 이하로 입력해주세요.")
    private String password;
}

 

※ Spring의 Bean Validation은 Default로 제공하는 Message들이 존재하고 임의로 수정할 수 있다.
     꼭 message를 설정해야 하는 것은 아니지만, 메시지를 직접 설정하면 사용자에게 더 친절한 안내할 수 있다.

 

@Valid vs @Validated 차이점

@Valid, @Validated는 Bean Validation Annotation이 있으면 검증을 수행한다.

  @Valid @Validated
제공처 Java 표준 (javax.validation) Spring 전용 (org.springframework.validation)
주요 용도 단순 객체 검증 (Controller에서 DTO 검증) 그룹 검증, Service 계층에서도 검증 가능
그룹 지정 ❌ 불가능 ✅ 가능 (groups = ...)
예외 종류 MethodArgumentNotValidException ConstraintViolationException
중첩 객체 검증 ✅ 가능 ✅ 가능
public ResponseEntity<?> createUser(@Valid @RequestBody UserDto dto) {
    ...
}

 

Validation 예외 처리

Validation 예외가 발생하면, Spring은 기본적으로 400 Bad Request + 간단한 에러 메시지를 응답으로 보내고 콘솔에는 예외 로그를 출력한다.

 

하지만 이런 방식은 사용자에게 불친절하고, 에러 정보를 잘 활용하지 못한다. 따라서, 사용자에게 친절하고 명확한 메시지를 제공하고, 서버에 더 자세한 로그를 남기기 위해 BindingResult나 @ControllerAdvice를 사용해 직접 예외를 처리해 주는 게 좋다.

 

1) BindingResult 사용하기

Validation Error가 발생하면 FieldError, ObjectError를 생성하여 BindingResult에 담아주며, 이를 통해 예외를 처리할 수 있다.

@PostMapping("/users")
public ResponseEntity<?> createUser(@Valid @RequestBody UserDto dto, BindingResult bindingResult) {
    
    // 1. 유효성 검사 실패 시 처리
    if (bindingResult.hasErrors()) {
        // 필드별 오류 메시지 수집
        Map<String, String> errors = new HashMap<>();
        bindingResult.getFieldErrors().forEach(error -> {
            errors.put(error.getField(), error.getDefaultMessage());
        });
        
        // 오류 내용을 포함한 응답 반환
        return ResponseEntity.badRequest().body(errors);
    }

    // 2. 정상 로직 수행
    return ResponseEntity.ok("회원가입 성공!");
}

 

2) GlobalExceptionHandler 사용하기

GlobalExceptionHandler를 사용하여 예외가 발생하면 처리하도록도 설정할 수 있다.

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
    Map<String, String> errors = new HashMap<>();
    ex.getBindingResult().getFieldErrors().forEach(error ->
            errors.put(error.getField(), error.getDefaultMessage())
    );

    return ResponseEntity.badRequest().body(errors);
}

 

groups 

Bean Validation의 groups 속성은 다양한 유효성 검사 시나리오를 정의할 때 사용된다. 동일한 객체에 대한 검증을 상황에 따라 다르게 적용하고 싶을 때 groups를 활용할 수 있다.

public class UserDto {

    @NotBlank(message = "이름은 필수입니다.", groups = JoinGroup.class)
    private String name;

    @NotBlank(message = "비밀번호는 필수입니다.", groups = JoinGroup.class)
    @Size(min = 6, message = "비밀번호는 6자 이상이어야 합니다.", groups = JoinGroup.class)
    private String password;

    @NotBlank(message = "이메일은 필수입니다.", groups = {JoinGroup.class, LoginGroup.class})
    private String email;
}
// 회원가입 검증
@PostMapping("/join")
public ResponseEntity<?> join(@Validated(JoinGroup.class) @RequestBody UserDto dto, BindingResult bindingResult) {
    ...
}

// 로그인 검증
@PostMapping("/login")
public ResponseEntity<?> login(@Validated(LoginGroup.class) @RequestBody UserDto dto, BindingResult bindingResult) {
    ...
}

 

groups 사용 vs DTO 분리

groups는 상황별 유효성 검증이 가능하다는 장점이 있지만,

실무에서는 유지보수성과 명확성 때문에 DTO 자체를 분리해서 사용하는 게 적절하다.

  groups 사용 DTO 분리
개념 하나의 DTO에 상황별 유효성 검증을 그룹으로 나눠서 적용 저장용 DTO, 수정용 DTO 등으로 아예 클래스를 분리
장점 코드 중복을 줄일 수 있음 명확한 역할 분리, 가독성 ↑, 유지보수 쉬움
단점 가독성 ↓, 복잡도 ↑, 실무에선 거의 안 씀 중복되는 필드가 생길 수 있음
실무 적용 거의 사용 X (간단한 프로젝트 제외) ✅ 실무에서는 대부분 이 방법 사용
     
권장 네이밍 - SaveRequestDto, UpdateRequestDto 등 역할 기반 명명

'Spring' 카테고리의 다른 글

[Spring] JPA 시작하기  (0) 2025.06.04
[Spring] 쿠키 / 세션 / 토큰  (0) 2025.05.17
[Spring] Spring Bean 등록과 의존성 주입  (0) 2025.05.15
[Spring] 예외처리  (1) 2025.05.14
[Spring] JDBC 시작하기  (0) 2025.05.14