스프링(Spring), 스프링부트(SpringBoot)/스프링부트(SpringBoot) 기초

스프링부트 핵심 가이드(장정우 지음) - 유효성 검사(2)

개발학생 2025. 11. 10. 16:05
반응형

*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성  

 

[Java] 차근차근 Java 설치하기 (JDK17, Window 11)

자바 개발 도구 설치 방법에 대해서 알아보겠습니다. Java17은 LTS(Long Term Support : 장기 지원) 릴리즈로 1년 후까지 기술 지원 및 버그를 개선한 서비스를 제공받을 수 있습니다. 업데이트 버전을 꾸

yungenie.tistory.com

 

[Windows] Spring Tool Suite 4(STS 4) 다운로드 및 설치

STS란?Spring Tool Suite(STS)는 스프링 프로젝트를 생성하고, 개발할 수 있게 해주는 도구입니다. STS 설치 과정에 대해 설명드리겠습니다. 설치 파일 다운로드STS 공식 사이트에서 설치 파일을 다운로

priming.tistory.com

 

MySQL :: Download MySQL Community Server

Select Version: 9.3.0 Innovation 8.4.5 LTS 8.0.42 Select Operating System: Select Operating System… Microsoft Windows Ubuntu Linux Debian Linux SUSE Linux Enterprise Server Red Hat Enterprise Linux / Oracle Linux Fedora Linux - Generic Oracle Solaris mac

dev.mysql.com

  • Gradle

**STS에서 Gradle 프로젝트 생성한 과정 

*** 함께 보면 좋은 글

2025.07.10 - [스프링(Spring), 스프링부트(SpringBoot)/스프링부트(SpringBoot) 기초] - 스프링 부트 핵심 가이드(장정우 지음) - 스프링 부트 개요

 

스프링 부트 핵심 가이드(장정우 지음) - 스프링 부트 개요

1. 스프링 프레임워크자바(Java) 기반 애플리케이션 프레임워크로, 엔터프라이즈급(기업 환경 대상 개발) 애플리케이션을 위한 다양한 기능 제공-> 오픈소스 경량급 애플리케이션 프레임워크로

keep-programming-study.tistory.com

2025.07.11 - [스프링(Spring), 스프링부트(SpringBoot)/스프링부트(SpringBoot) 기초] - 스프링 부트 핵심 가이드(장정우 지음) - 개발에 앞서 알면 좋은 기초 지식

 

스프링 부트 핵심 가이드(장정우 지음) - 개발에 앞서 알면 좋은 기초 지식

1. 서버 간 통신마이크로서비스 아키텍처에서 한 서버가 다른 서버에 통신을 요청하는 것을 의미-> 한 대는 서버/다른 한 대는 클라이언트가 됨 가장 많이 사용되는 방식은 HTTP/HTTPS 방식(TCP/IP, SOA

keep-programming-study.tistory.com

2025.07.16 - [스프링(Spring), 스프링부트(SpringBoot)/스프링부트(SpringBoot) 기초] - 스프링 부트 핵심 가이드(장정우 지음) - REST API 명세를 문서화하는 방법(Swagger), 로깅 라이브러리(Logback)

 

스프링 부트 핵심 가이드(장정우 지음) - REST API 명세를 문서화하는 방법(Swagger), 로깅 라이브러리

*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성 Windows11(윈도우 11) 환경자바 JDK 17 버전 설치 https://yungenie.tistory.com/11 [Java] 차근차근 Java 설치하기 (JDK17, Window 11)자바 개발 도구 설치 방법

keep-programming-study.tistory.com

2025.10.24 - [스프링(Spring), 스프링부트(SpringBoot)/스프링부트(SpringBoot) 기초] - 스프링부트 핵심 가이드(장정우 지음) - 유효성 검사(1)

 

스프링부트 핵심 가이드(장정우 지음) - 유효성 검사(1)

*책 내용과 다르게, 다음과 같은 환경에서 프로젝트 생성 Windows11(윈도우 11) 환경자바 JDK 17 버전 설치 https://yungenie.tistory.com/11 [Java] 차근차근 Java 설치하기 (JDK17, Window 11)자바 개발 도구 설치 방법

keep-programming-study.tistory.com

 

1. @Validated 활용

  • 스프링에서는, 자바에서 지원하는 @Valid 어노테이션과는 별도의 어노테이션으로 유효성 검사 지원
    -> @Valid 어노테이션의 기능을 포함하고 있으므로, @Valid를 @Validated로 변경 가능
  • @Validated에서는 유효성 검사를 그룹으로 묶어, 대상을 특정할 수 있는 기능이 있음

*스프링 부트에서 예제 생성

  • data 패키지 내에 group 패키지 생성 후, ValidationGroup1과 ValidationGroup2라는 인터페이스 생성
    -> 두 인터페이스 모두 내부 코드 없이, 인터페이스만 생성한 후 그룹화하는 용도

(1) 검증 그룹 설정-DTO 객체 새로 생성 

package com.example.demo.jpa.data.dto;

import com.example.demo.jpa.data.group.ValidationGroup1;
import com.example.demo.jpa.data.group.ValidationGroup2;

import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

//DTO 클래스: 클라이언트 요청 데이터를 담는 용도
@Data // Lombok: getter/setter, equals, hashCode, toString 자동 생성
@NoArgsConstructor // Lombok: 기본 생성자 자동 생성
@AllArgsConstructor // Lombok: 모든 필드를 받는 생성자 자동 생성
@ToString // Lombok: toString 메서드 자동 생성
@Builder // Lombok: 빌더 패턴 지원
public class ValidatedRequestDto {
	// name은 반드시 값이 있어야 함 (null 또는 빈 문자열 불가)
	@NotBlank
	private String name;

	// email은 이메일 형식이어야 함 (예: user@example.com)
	@Email
	private String email;

	// 휴대폰 번호는 정규식에 맞아야 함: 010, 011, 016~019로 시작하고 중간에 - 또는 . 가능
	// 예: 010-1234-5678 또는 011.2345.6789
	@Pattern(regexp = "01(?:0|1|[6-9])[.-]?(\\d{4})[.-]?(\\d{4})$")
	private String phoneNumber;

	// age는 ValidationGroup1일 때 최소 20 이상이어야 함
	@Min(value = 20, groups = ValidationGroup1.class)
	// age는 ValidationGroup2일 때 최대 40 이하이어야 함
	@Max(value = 40, groups = ValidationGroup2.class)
	private int age;

	// description은 길이가 0~40자 사이여야 함
	@Size(min = 0, max = 40)
	private String description;

	// count는 ValidationGroup2일 때 양수여야 함 (0보다 커야 함)
	@Positive(groups = ValidationGroup2.class)
	private int count;

	// booleanCheck는 반드시 true여야 함 (예: 체크박스가 선택된 상태)
	@AssertTrue
	private boolean booleanCheck;
}

 

(2) ValidationController.java에 코드 추가

마찬가지로 전에 STS4에서 @RequestBody가 죽어도 먹히지 않는 지옥을 경험했기 때문에,

책 내용과 다르게 @RequestParam 방식의 코드 작성... 하려고 했으나 이전 글의 코드와 거의 비슷하기 때문에, 테스트를 돌리지 않고 코드만 작성하였습니다.
-> 실제로는 보안에 좋지 않기 때문에 IntelliJ를 써서 @RequestBody로 받는게 맞습니다

package com.example.demo.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.demo.jpa.data.dto.ValidatedRequestDto;
import com.example.demo.jpa.data.group.ValidationGroup1;
import com.example.demo.jpa.data.group.ValidationGroup2;

import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

@RestController // REST API 컨트롤러로 동작함을 나타냄 (@Controller + @ResponseBody)
@RequestMapping("/validation") // 이 컨트롤러의 기본 URL 경로를 "/validation"으로 설정
public class ValidationController {

    // SLF4J 로깅 인터페이스를 사용하여 로그를 출력할 수 있도록 Logger 객체 생성
    private final Logger LOGGER = LoggerFactory.getLogger(ValidationController.class);
    
    ... 생략 ...

    /**
     * 기본 유효성 검사
     * - DTO 전체에 대해 유효성 검사를 수행
     * - DTO 클래스에 선언된 모든 제약 조건을 검사
     * - 유효성 그룹을 지정하지 않기 때문에 모든 필드가 검사 대상
     */
    // POST 요청을 "/validation/validated" 경로로 매핑 
    @PostMapping("/validated") 
    public ResponseEntity<String> checkValidation( 
    	@Validated @RequestBody ValidatedRequestDto validatedRequestDto
    ) { 
    	LOGGER.info(validatedRequestDto.toString()); 
    	return ResponseEntity.status(HttpStatus.OK).body(validatedRequestDto.toString()); 
    }
    
    /**
     * 그룹1 기반 유효성 검사
     * - ValidationGroup1 인터페이스에 해당하는 필드만 검사
     * - DTO 클래스에서 각 필드에 `groups = ValidationGroup1.class`로 지정된 제약 조건만 적용
     */
    // POST 요청을 "/validation/validated/group1" 경로로 매핑
    @PostMapping("/validated/group1")
    public ResponseEntity<String> checkValidation1(
    	@Validated(ValidationGroup1.class) @RequestBody ValidatedRequestDto validatedRequestDto
    ) {
    	LOGGER.info(validatedRequestDto.toString());
    	return ResponseEntity.status(HttpStatus.OK).body(validatedRequestDto.toString());
    }
    
    /**
     * 그룹2 기반 유효성 검사
     * - ValidationGroup2 인터페이스에 해당하는 필드만 검사
     * - DTO 클래스에서 `groups = ValidationGroup2.class`로 지정된 필드만 검사 대상입
     */
    // POST 요청을 "/validation/validated/group2" 경로로 매핑
    @PostMapping("/validated/group2")
    public ResponseEntity<String> checkValidation2(
    	@Validated(ValidationGroup2.class) @RequestBody ValidatedRequestDto validatedRequestDto
    ) {
    	LOGGER.info(validatedRequestDto.toString());
    	return ResponseEntity.status(HttpStatus.OK).body(validatedRequestDto.toString());
    }
}

 

2. 커스텀 Validation 추가

  • 실무에서는 유효성 검사를 할 때 자바/스프링의 유효성 검사 어노테이션에서 제공하지 않는 기능을 써야 할 때도 있는데,
    이 경우 ConstraintValidator와 커스텀 어노테이션을 조합하여 별도 유효성 검사 어노테이션 생성

1) Telephone 어노테이션 인터페이스 생성

package com.example.demo.config.annotation;

import jakarta.validation.Constraint;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;


/**
 * 커스텀 유효성 검사 어노테이션 정의: @Telephone
 * - 전화번호 형식을 검증하기 위한 어노테이션
 * - 해당 어노테이션이 붙은 필드는 TelephoneValidator 클래스에 의해 유효성 검사를 받음
 */
@Target(ElementType.FIELD) // 이 어노테이션은 필드에만 적용됩니다.
@Retention(RetentionPolicy.RUNTIME) // 런타임 시에도 어노테이션 정보가 유지되어야 합니다.
@Constraint(validatedBy = TelephoneValidator.class) // 실제 유효성 검사 로직을 수행할 클래스 지정
public @interface Telephone {

    /**
     * 유효성 검사 실패 시 반환할 기본 메시지
     * - 필요 시 @Telephone(message = "커스텀 메시지")로 오버라이드 가능
     */
    String message() default "전화번호 형식이 일치하지 않습니다.";

    /**
     * 유효성 검사 그룹 지정
     * - Bean Validation의 그룹 기능을 사용할 때 활용
     */
    Class[] groups() default {};

    /**
     * 추가적인 메타 정보 전달용
     * - 기본적으로 잘 사용되지는 않지만, 확장 시 활용 가능
     */
    Class[] payload() default {};
}

2) 전화번호 형식이 일치하는지 확인하는 유효성 검사 어노테이션 생성

package com.example.demo.config.annotation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

/**
 * 커스텀 유효성 검사 로직 클래스: TelephoneValidator
 * - @Telephone 어노테이션이 붙은 필드에 대해 실제 유효성 검사를 수행
 * - ConstraintValidator<Telephone, String>을 구현하여 String 타입의 전화번호를 검사
 */
public class TelephoneValidator implements ConstraintValidator<Telephone, String> {

    /**
     * 유효성 검사 로직을 구현하는 메서드
     * @param value 검사 대상 값 (전화번호 문자열)
     * @param context 유효성 검사 컨텍스트 (메시지 설정 등 가능)
     * @return true: 유효한 값 / false: 유효하지 않은 값
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 값이 null이면 유효하지 않음
        if (value == null) {
            return false;
        }

        // 정규식을 통해 전화번호 형식 검사
        // 예: 010-1234-5678, 011.234.5678 등
        return value.matches("01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4}))$");
    }
}

3) ValidatedRequestDto 클래스에 적용

package com.example.demo.jpa.data.dto;

import com.example.demo.config.annotation.Telephone;
import com.example.demo.jpa.data.group.ValidationGroup1;
import com.example.demo.jpa.data.group.ValidationGroup2;

import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

//DTO 클래스: 클라이언트 요청 데이터를 담는 용도
@Data // Lombok: getter/setter, equals, hashCode, toString 자동 생성
@NoArgsConstructor // Lombok: 기본 생성자 자동 생성
@AllArgsConstructor // Lombok: 모든 필드를 받는 생성자 자동 생성
@ToString // Lombok: toString 메서드 자동 생성
@Builder // Lombok: 빌더 패턴 지원
public class ValidatedRequestDto {
	@NotBlank
	private String name;

	@Email
	private String email;

	// 휴대폰 번호는 정규식에 맞아야 함: 010, 011, 016~019로 시작하고 중간에 - 또는 . 가능
	// - 검증식은 직접 생성한 어노테이션으로 적용
	@Telephone
	private String phoneNumber;
	
	@Min(value = 20, groups = ValidationGroup1.class)
	@Max(value = 40, groups = ValidationGroup2.class)
	private int age;

	@Size(min = 0, max = 40)
	private String description;

	@Positive(groups = ValidationGroup2.class)
	private int count;

	@AssertTrue
	private boolean booleanCheck;
}

 

 

 

 

 

반응형