본문 바로가기
Develop Study/Java 2025. 6. 5.

(25.06.05) record 클래스 & DTO 활용

WebSocket 기반의 대화방에서 채팅을 구현하는데 있어서,

Subscribe 한 클라이언트들에게 채팅을 보내는데 사용하는 responseDTO 클래스를 record 에 사용하는 것으로 참고자료에서 나타냈다.

하지만, DTO에서 사용되는 컴포넌트들을 그대로 축약했다고 생각한 클래스라고 치부하기 보다는

좀더 고려를 해야하는 부분이 많아서

Java 에 최신으로 등재된 record 클래스에 대해서 설펴보고,

중간중간 나의 궁금한 점들을 "주의 사항" 내지는 표로 정리했다.

 


record 클래스

  • boilerplate 코드를 줄이기 위해서 자동으로 필요한 객체들과 여러 필요한 내용을 생성해주는 특수한 불변 데이터 클래스(immutable data class)
    • Java 14 preview 버전, Java 16 정식 버전(standard feature)
    • 모든 필드를 컴포넌트로 명시
  • final 이기 때문에 당연히 다른 클래스 상속 또는 상속 받을 수 없음
    • Setter가 없기 때문에 기존에 만들어진 객체의 필드값을 update 할 수 있는 설계와는 맞지 않음
    • 기본 생성자 존재 X → 불변성과 필드 초기화를 한번 이뤄지는 설계 철학에 의해 = 모든 컴포넌트의 값이 존재해야한다는 것
    • 모든 필드를 파라미터로 하는 생서자 = Canonical Constructor 만 존재
  • 기존의 객체지향 설계의 POJO, Plain Old Java Object 구조에서 → 데이터 저장, 데이터 전달 목적의 클래스를 간결하게 표기 하기 위해 만들어진 구조
    • DTO(Data Transfer Object)
    • VO(Value Object)
    • Config 설정 정보 등 불변 데이터 구조
  • 자동 생성
    • 모든 필드의 생성자
      • 오버로딩 불가능
    • getter 메서드
    • equals()hashCode()
    • toString()

example

// 기존의POJO 구성의 클래스
public final class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String name() { return name; }
    public int age() { return age; }

    @Override
    public boolean equals(Object o) { ... }
    
    @Override
    public int hashCode() { ... }
    
    @Override
    public String toString() { ... }
}
// record 클래스
public record Person(String name, int age) {}
  • 기본적으로는 body는 빈칸으로 둠
    • abstract 의 뜻이 아니라 자동으로 생성되기 때문에 남김
    • 컴팩트 생성자를 추가로 작성 가능

POJO vs record

항목 POJO record
목적 동적으로 데이터 저장/수정/조작 불변/고정 데이터 저장
필드 수정 가능 (setter 또는 update 메서드 등) 불가능 (모든 필드는 final)
코드 getter/setter/생성자 등 수동으로 모두 작성 자동 생성
상속 가능 불가능 (final)
사용 버전 Java 1.0부터 Java 14+ (정식은 Java 16+)
생성자 유연성 Overloading, default 등 다양하게 사용이 가능 모든 필드가 한번에 초기화 해야하므로 제한적

 

컴팩트 생성자, Compact constructor

  • record 클래스의 클래스에서 자동으로 생성된 생성자와 메서드에서, 필드에 대해 생성 시 검증/로직을 수행하고 싶을 때 사용
  • 일반 constructor와 다르게 필드 이름만으로 정의

기본 컴팩트 생성자 구조

public record Person(String name, int age) {
    public Person {
        if (age < 0) {
            throw new IllegalArgumentException("나이는 0 이상이어야 합니다.");
        }

        name = name.strip(); // 공백으로 취급되는 모든 문자 제거
    }
}
  • 파라미터(매개변수)를 명시X
    • Person(String name, int age) 대신 Person { ... }
  • 컴파일러가 자동으로 파라미터를 필드에 할당하기 전, 또는 후에 원하는 로직 삽입 가능
    • 특정 필드 값에 대해서의 조건을 설정 가능
  • 내부적으로 this.name = name; 등의 기본 필드 초기화는 자동 수행
  • Setter 구조가 아니기 때문에 다시 this.name = ... 하려고 하면 컴파일 오류 발생

static 팩토리 메서드를 통한 객체 생성 : overloading 대응

public record User(String name, String email) {
    public User {
        name = name.trim();
        email = email.toLowerCase();
    }

    public static User fromRaw(String rawName, String rawEmail) {
        return new User(rawName, rawEmail);
    }
}
  • record 설계 철학에 의해 new User() 과 같이 기본 생성자를 활용해서 새로운 객체를 만들 수 없기 때문에 지정할 수 있음
  • Overloading 을 대체하는 역할
    • 모든 컴포넌트 값들을 모두 기재를 할 경우 새로운 객체로 계속 만들어 낼 수 있음

record 직렬화

  • record는 모든 필드가 생성자 인자로 한번에 들어가야 함
    • 정확하게 데이터를 전달을 할 때 안정적으로 전달을 할 수 있음
    • 무결성을 유지시킬 수 있음
  • JSON 데이터에 필드가 누락되거나 타입이 맞지 않으면 역직렬화 실패
  • 안정적인 특징으로 DTO 에 많이 사용가능

Request DTO 에 record 적용

  • 응답 객체는 보통 가변적이고 점진적으로 완성, 동적으로 값이 계속 바뀌기 때문에, record에 부적합할 수 있음

validation 적용

POJO 방식

@Getter
public class ChatRoomRequestDTO {

    @NotBlank(message = "대화방 제목은 필수 입력해주세요.")
    private String title;

    @NotBlank(message = "대화방 카테고리는 필수 입력해주세요.")
    private String category;

    private Boolean isPrivate;

    private String password;
}

record

public record ChatRoomRequestDTO(
    @NotBlank(message = "대화방 제목은 필수 입력해주세요.")
    String title,

    @NotBlank(message = "대화방 카테고리는 필수 입력해주세요.")
    String category,

    Boolean isPrivate,
    String password
) {}
  • Validation (@Valid) 까지 모두 포함해서 매개변수로 적용

주의

  • @Setter와 기본 생성자가 없기 때문에, model.addAttribute() 를 사용하는 FORM 요청에서 사용 불가
    • FORM 요청은 HTTP POST 방식으로 전송되며, Spring MVC에서는 보통 **@ModelAttribute**를 이용해서 기본 생성자 + setter로 객체를 구성
  • AJAX 방식 = JSON을 사용하는 구조를 취급해야함
    • 또는 POJO로 교체해야함

참고자료

https://docs.oracle.com/en/java/javase/21/language/records.html

 

Java Language Updates

Record classes, which are a special kind of class, help to model plain data aggregates with less ceremony than normal classes.

docs.oracle.com

 

 


AJAX 를 사용해서 FE와 데이터를 request, response 되는 환경, 그리고 Response DTO를 Setter로 계속 업데이트가 없을 경우 모든 DTO 는 record로 바꾸면서 보일러플레이트를 만들어도 효율적으로 작성할 수 있을 것이다.