[Spring In Action] Spring MVC의 데이터 바인딩과 유효성 검사, 그리고 Modern Java(Record) 활용

1. 문제 제기 (Introduction & Problem Statement)

  • 관찰 현상 또는 질문:
    Spring Boot로 웹 애플리케이션을 개발하다 보면 데이터 전송 객체(DTO)를 정의할 때 습관적으로 Lombok의 @Data를 사용하거나, 단순히 @NotNull 어노테이션을 붙여 유효성 검사를 수행하곤 한다. 하지만 *"왜 DTO에는 @Data 대신 Java 14의 Record 사용이 권장되는가?", *"Validation은 DB가 아니라 언제, 어디서 수행되는가?", *"MVC 패턴에서 Model과 View 사이의 데이터 바인딩은 구체적으로 어떻게 이루어지는가?"* 와 같은 근본적인 질문에 부딪히게 된다.

  • 탐구 목표:
    본 아티클에서는 Spring MVC에서의 데이터 흐름(Data Binding)과 유효성 검사(Bean Validation)의 내부 동작 원리를 분석한다. 또한, Java의 EnumRecord가 제공하는 타입 안정성과 불변성이 시스템 아키텍처에 어떤 이점을 주는지 고찰하고, 실무적인 관점에서의 사용 전략을 정리하는 것을 목표로 한다.


2. 기술 분석 및 핵심 원리 (Technical Deep Dive)

2-1. 기본 개념: Modern Java와 Spring의 데이터 처리

데이터를 다루는 방식은 언어 차원(Java)과 프레임워크 차원(Spring)에서 각각 발전해왔다.

  • Lombok @Data: @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor 5가지를 한 번에 제공하는 종합 선물 세트다. 편리하지만 무분별한 Setter 개방으로 데이터 불변성을 해칠 위험이 있다.
  • Java Enum (열거형): 서로 연관된 상수들의 집합을 정의하는 특수 참조 자료형이다. public static final 필드로 관리되어 컴파일 시점에 타입 안정성을 보장한다.
  • Spring Model: 컨트롤러가 비즈니스 로직을 수행한 후, View(타임리프 등)로 데이터를 전달하기 위해 사용하는 객체다.

2-2. 심층 분석: 데이터 운반과 유효성 검사의 원리

1. 불변 객체의 진화: @Data vs Record

과거에는 DTO 생성 시 Lombok의 @Data를 주로 사용했으나, 최근에는 Java 14+의 Record 도입이 늘고 있다.

// 1. Lombok @Data 방식 (가변성 존재)
@Data
public class UserDto {
    private String name; // Setter로 변경 가능
}

// 2. Java Record 방식 (불변성 보장)
public record UserRecord(String name) {
    // 컴파일러가 final 필드, 생성자, Getter(name()), toString 등을 자동 생성
    // Setter가 존재하지 않음 -> 데이터 운반(Carrier) 목적에 충실
}
  • 분석: Record는 데이터의 '운반'이라는 목적에 맞춰 설계되었다. Setter가 아예 제공되지 않으므로 생성 시점 이후 데이터 변경이 불가능(Immutable)하여, 멀티스레드 환경이나 데이터 흐름 추적에서 훨씬 안전하다.

2. Spring MVC의 데이터 바인딩 (@ModelAttribute)

Spring MVC는 @ModelAttribute를 통해 HTTP 요청 파라미터를 자바 객체에 바인딩하고, 이를 다시 View로 전달하는 Model에 자동으로 담는다.

  • 동작 순서:
  1. DispatcherServlet이 요청을 받음.
  2. HandlerAdapter가 컨트롤러 메서드 호출 전 @ModelAttribute가 붙은 메서드나 인자를 확인.
  3. 요청 파라미터(Query String, Form Data)를 객체의 필드에 바인딩 (Setter 혹은 생성자 사용).
  4. 바인딩된 객체는 자동으로 ModeladdAttribute 되어 View(HTML)에서 접근 가능해짐.

3. 유효성 검사 (Bean Validation)의 동작 위치

@NotNull, @Size 같은 어노테이션은 DB 제약조건이기도 하지만, Spring에서는 애플리케이션 레이어에서 먼저 걸러진다.

  • Jakarta Validation (구 Javax): 인터페이스 표준.
  • Hibernate Validator: 실제 구현체.
  • 검증 시점: DB Insert 시점이 아니라, 컨트롤러에 데이터가 들어오는 시점(@Valid가 붙은 파라미터 바인딩 시점)에 DispatcherServlet 레벨에서 수행된다.

2-3. 해결 방안 및 아키텍처 Trade-offs

개발 과정에서 마주치는 선택지들에 대한 비교 분석이다.

A. Converter 구현 위치: @Component vs @Service

구분 @Component (Converter) @Service (Business Logic)
역할 데이터 타입 변환 (예: String <-> Enum) 핵심 비즈니스 로직 처리
성격 인프라성, 유틸리티 성격 도메인 로직, 트랜잭션 관리
권장 Spring MVC Converter 인터페이스 구현 시 사용 비즈니스 흐름 제어 시 사용

Note: Spring Web Converter(HTTP 요청 변환)와 JPA Converter(DB 데이터 변환)는 구분해서 사용해야 한다. 전역적인 변환이 필요하다면 WebMvcConfigurer에 등록하여 관리하는 것이 효율적이다.

B. 템플릿 엔진 선택: Thymeleaf vs Mustache

구분 Thymeleaf Mustache
특징 속성 기반: HTML 태그에 속성을 추가하여 동작 Logic-less: 로직이 거의 없는 템플릿
장점 퍼블리싱 된 HTML 파일을 그대로 브라우저에서 열어볼 수 있음 (협업 유리) 문법이 매우 단순하고 학습 곡선이 낮음
SSR/SEO 서버 사이드 렌더링을 통해 완성된 HTML을 제공하므로 검색 엔진 최적화(SEO)에 유리함 동일함

3. 결론 및 고찰 (Conclusion & Takeaways)

  • 핵심 요약:
  • DTO와 같은 데이터 운반체는 가변적인 @Data보다는 불변성을 보장하는 Record를 사용하는 것이 현대적인 Java 개발 트렌드이자 안전한 방식이다.
  • Spring의 유효성 검사(@Valid)는 DB 도달 전 애플리케이션 앞단(DispatcherServlet)에서 수행되므로 불필요한 DB 부하를 막는 방패 역할을 한다.
  • @ModelAttribute는 요청 데이터 바인딩과 View 데이터 전달이라는 두 가지 역할을 동시에 수행하여 컨트롤러 코드를 간결하게 만든다.
  • 기술적 통찰 및 나의 생각:
    이번 학습을 통해 프레임워크가 제공하는 '편의성' 뒤에 숨겨진 '동작 원리'를 이해하는 것이 중요함을 깨달았다. 특히 Arrays.asList가 고정 크기 리스트를 반환한다는 점이나, 엔티티 ID를 그대로 노출할 경우 발생할 수 있는 IDOR(부적절한 직접 객체 참조) 보안 취약점은 실무에서 간과하기 쉬운 부분이다. 단순히 기능을 구현하는 것을 넘어, 데이터의 불변성과 보안을 고려한 설계가 필요하다.
  • 향후 과제:
  • 실무에서 엔티티 ID 노출을 막기 위한 암호화 ID 또는 UUID 도입 전략은 어떻게 구현하는가?
  • SSR(서버 사이드 렌더링) 환경과 CSR(클라이언트 사이드 렌더링-Vue.js) 환경을 혼합해서 사용할 때, 인증/인가(Session vs JWT) 처리는 어떻게 달라지는가?