[Java] Record

SIK407·2025년 1월 15일

Java

목록 보기
5/5

레코드~ 레코드~ 끽! 끽! 끽! (죄송합니다 ㅠㅠㅠㅠ)



KakaoPay로 결제수단을 연결 도중, 어떤 블로그에서 DTO를 Class가 아닌 Record로 정의했다.

DTO (Data Transfer Object, 데이터 전송 객체)
프로세스 간에 데이터를 전달하는 객체를 의미
말 그대로 데이터를 전송하기 위해 사용하는 객체
(예시로, Spring에서 React로 Response를 하기 위해 담아서 보내는 데이터 객체)

어? 이게 뭐지 언제 나온거지?

이걸 사용하니까 그 Java의 더러운 BoilerPlate 코드가 확 줄기에 한번 알아보고자 한다.

📌 언제부터? 정확하게 Record가 뭐야?

Record란 자바 14에서 preview로 추가된 이후, 16버전에서 정식 스펙이 됐다.
처음 소개된 새로운 Class 타입이다.

파이썬의 Dictionary, Kotlin의 Data Class처럼 값의 집합으로 이루어진 간단한 객체를 심플하게 개발하기위해 고안되었다고 한다.

그래서 불변 데이터를 다룰 class 구현에 최적화되어있다.

💽 Record 특징

  1. Final 클래스다. 다른 Class를 상속을 하거나 받을 수 없다.
  2. 자동생성되는 Accessor 함수명은 인스턴스 멤버 변수의 이름과 같다.
  3. 접근제어자는 public, package-private만 가능
  4. Record 접근제어자 > Class 접근제어자

💽 Record 구조

@Getter
public class LoginReqDTO {
    private final String email;
    private final String password;
}

여기 로그인을 위해 프런트에서 아이디와 비밀번호를 받아오는 DTO가 있다.
Final Class여서 한번 들어온 값은 변할 수 없다. (불변 클래스)

이 Class를 Record로 변환을 시키면...

public record LoginReqDTO(String email, String password) {
}

엄청 코드가 간결해진다.

구조를 살펴보면, class 대신 record를 사용한다.
(Header), {Body}의 구조를 가지는데 Header에 나열되는 필드컴포넌트라고 부른다.

컴파일러는 Header를 통해 필드를 추론한다. (String 타입의 email, password 추론)
추론 후, 우리가 많이 쓰는 @Getter, @Setter, toString 등을 선언하지 않아도 자동으로 제공하게 된다.

🔨 생성자

기본적으로 record는 email과 password 필드를 매개변수로 받는 생성자를 자동으로 제공한다.
만약 다른 Class를 이용해서 저 객체를 초기화해야하는 경우면 어떻게 해야할까?

일단 컴펙트 생성자라는 개념이 있다.

컴펙트 생성자

public record LoginReqDTO(String email, String password) {
	public LoginReqDTO {
        Objects.requireNonNull(email);
        Objects.requireNonNull(password);
    }
}

Objects.requireNonNull()은 객체가 null이면 NullPointerException을 던지는 메서드다.

개발자가 명시적으로 인스턴스를 초기화 하지 않아도, 컴팩트 생성자의 마지막 부분에 초기화 구문이 자동으로 삽입된다.
일반적으로 사용하는 표준 생성자와는 달리 컴팩트 생성자 내부에서는 인스턴스 필드에 접근할 수 없다.
(아마 Setter로 접근하는 듯 싶다)

그래서 컴팩트 생성자는 null 값이 들어오는 것을 막기 위한 용도인 것 같다.

다른 객체로 생성자를 사용할 경우!

public record LoginReqDTO(String email, String password) {
	public LoginReqDTO (User user) {
        user.getEmail(),
        user.getPassword()
    }
}

이게 지금 적합한 예시는 아니지만, ResponseDto 같은 경우는 Entity로 초기화를 하는 생성자가 있으면 엄청 편하다.

그럴 경우엔 저 예시코드처럼 선언하면 된다.

물론 사용법은 이와 같다.

LoginReqDTO loginReqDTO = new LoginReqDTO("test123@naver.com", "1234");

User testUser = new User("test123@naver.com", "1234"); // 이런 Class가 있다고 가정
LoginReqDTO loginReqDTO = new LoginReqDTO(testUser);

그럼 이 좋은 기능들이 있는 record를 Entity로 사용하면 되겠네????

❌ Entity로는 사용이 안된다...;

이유는 다음과 같다.

1. 접근자 메소드가 필수 명명 규칙을 따르지 않는다

// record
String email = loginReqDTO.email();

// @Getter
String email = loginReqDTO.getEmail();

record의 getter는 필드명을 그대로 사용하고 있다.
쿼리 사용 후, 가져온 데이터를 접근자(Getter)로 초기화(Setter)를 진행한다.
메소드 이름이 다르니, 쿼리 입장에서는 접근도 초기화도 못하는 아이러니한 상황....;;

2. 쿼리 결과를 매핑할 때 객체를 인스턴스화 할 수 있도록 매개변수가 없는 생성자가 필요하다.

JPA를 사용할 때 엔티티 클래스에는 기본 생성자(no-args constructor)가 필수다.

// 기본 생성자와 커스텀 생성자
protected User() {}

JPA가 DB에서 데이터를 조회하여 엔티티를 생성할 때, 리플렉션(Reflection)이라는 기술을 사용하기 때문인데....

결론부터 말하면 record는 기본 생성자(no-args constructor)를 지원하지 않는다.

3. 프록시를 생성하기 위해서 entity는 불변이면 안된다.

hibernate와 같은 JPA는 Proxy 생성을 위해 인수 생성자, non-final 필드, setter 및 non-final 클래스가 없는 엔티티에 의존한다.

근데, record는 final 클래스에 setter도 당연히 안되니...

😁 결론!

final 클래스인 DTO에만 record를 사용하자!

사용하면서 코틀린쓰는거 같아서 코드 작성할 맛이 났다.

더러운 보일러 코드....

profile
감자 그 자체

0개의 댓글