JPA 값 타입

유기훈·2025년 3월 8일

값 타입(Value Type) 이란?

  • 엔티티와 다르게 독립적인 식별자(@Id)를 가지지 않는 타입
  • 한 엔티티가 내부적으로 포함하는 객체로, 엔티티의 일부처럼 동작
  • JPA에서 값 타입을 활용하면 객체지향적인 설계를 유지하면서 중복을 줄일 수 있음

값 타입의 주요 특징

  • 엔티티와 다르게 식별자(@Id)가 없음
  • 생명 주기가 엔티티에 의존 (엔티티가 삭제되면 값 타입도 함께 삭제됨)
  • Setter를 제공하지 않고, 불변 객체(Immutable Object)로 설계하는 것이 권장됨

값 타입의 종류

JPA에서는 값 타입을 크게 세 가지로 구분함.

(1) 기본 값 타입
• int, String, Integer, Long 같은 기본형(primitive type)과 래퍼(wrapper) 타입
• 별도의 매핑 없이 바로 사용 가능

@Entity
public class User {
    @Id @GeneratedValue
    private Long id;

    private String name;  // 기본 값 타입
    private int age;      // 기본 값 타입
}

(2) 임베디드 값 타입 (@Embeddable)

  • 여러 개의 값 타입을 하나의 객체로 묶어 재사용하는 방식
  • 중복되는 필드를 하나의 클래스로 추출하여 재사용 가능
  • 복합적인 데이터를 깔끔하게 표현 가능

사용 방법:
1. @Embeddable : 값 타입을 정의
2. @Embedded : 엔티티에서 값 타입을 포함

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;

    protected Address() {} // JPA 기본 생성자 필요

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}
@Entity
public class User {
    @Id @GeneratedValue
    private Long id;

    private String name;

    @Embedded  // Address 값 타입 포함
    private Address address;

    protected User() {}

    public User(String name, Address address) {
        this.name = name;
        this.address = address;
    }
}
  • 엔티티의 코드가 간결해지고, 재사용성이 증가
  • Address는 독립적인 엔티티가 아니라 User의 일부로 동작
  • User가 삭제되면 Address도 함께 삭제됨

(3) 값 타입 컬렉션 (@ElementCollection)
• 컬렉션(List, Set, Map) 형태로 값 타입을 저장
• 별도의 테이블을 만들어 저장하며, 연관관계를 가지지 않음
• @ElementCollection과 @CollectionTable을 사용

예제: 사용자의 여러 개의 주소 저장

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;

    protected Address() {}

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}
@Entity
public class User {
    @Id @GeneratedValue
    private Long id;

    private String name;

    @ElementCollection  // 값 타입 컬렉션
    @CollectionTable(name = "user_address", joinColumns = @JoinColumn(name = "user_id")) 
    private List<Address> addresses = new ArrayList<>();

    public void addAddress(Address address) {
        addresses.add(address);
    }
}
  • 여러 개의 주소를 저장할 수 있지만, 독립적인 엔티티가 아니라 User 엔티티에 종속됨
  • User 삭제 시 Address 리스트도 함께 삭제됨 (CASCADE ALL 적용됨)

단점

  • @ElementCollection을 사용할 경우, 값이 변경될 때 기존 데이터를 모두 삭제 후 다시 INSERT
  • 대량의 데이터 변경이 빈번할 경우, 연관관계를 맺은 엔티티(@OneToMany)로 변경하는 것이 좋음

값 타입 사용 시 주의할 점

1. 값 타입은 불변 객체로 설계하는 것이 좋음

  • 값 타입이 변경될 경우, 다른 엔티티에도 영향을 줄 수 있음
  • 불변 객체로 만들어 새로운 객체를 생성하여 교체하는 방식이 바람직

📌 불변 객체로 설계하는 방법

@Embeddable
public class Address {
    private final String city;
    private final String street;
    private final String zipcode;

    protected Address() {}  // JPA 기본 생성자

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }

    // 값 변경 시 새로운 객체 생성
    public Address changeCity(String newCity) {
        return new Address(newCity, this.street, this.zipcode);
    }
}
user.setAddress(user.getAddress().changeCity("부산")); // 새로운 객체 할당
  • Setter를 제공하지 않고, 변경이 필요할 때 새로운 객체를 만들어 할당
  • 값 타입 공유 문제를 방지하여 예기치 않은 변경을 막을 수 있음

정리

✔ 값 타입은 엔티티가 아닌 “엔티티 내부의 데이터 묶음”
✔ 엔티티는 식별자가 있지만, 값 타입은 식별자가 없음
✔ 임베디드 타입(@Embeddable)을 사용하면 객체지향적인 설계가 가능
✔ 값 타입은 불변 객체로 설계하는 것이 안전
✔ 값 타입 컬렉션(@ElementCollection)은 편리하지만 대량 변경 시 비효율적일 수 있음

profile
개발 블로그

0개의 댓글