Backend/JPA

JPA - 값 타입 매핑: 기본 개념부터 실무 적용까지

개발자-제이 2025. 2. 27. 15:13

 

1. 기본 값 타입

JPA에서 데이터 타입은 엔티티 타입값 타입으로 나뉜다.

엔티티 타입

  • @Entity로 정의된 객체
  • 식별자(ID)가 있어 데이터를 지속적으로 추적할 수 있음
  • 변경되더라도 ID가 유지되므로 동일한 엔티티로 인식
  • 예시: Member, Order 등

값 타입

  • int, double, String 같은 단순한 값
  • 식별자가 없고 변경 시 추적 불가능
  • 예시: 주소(Address), 근무 기간(Period)

값 타입 분류

  1. 기본 값 타입: int, Integer, String
  2. 임베디드 타입(복합 값 타입): 여러 값 타입을 묶어서 사용 (@Embeddable)
  3. 컬렉션 값 타입: 값 타입을 컬렉션으로 저장 (@ElementCollection)

 

기본 값 타입 예제

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

    private String name;  // 값 타입 (String)
    private int age;      // 값 타입 (int)
}

값 타입은 엔티티의 생명 주기에 의존하며, 엔티티가 삭제되면 값도 함께 삭제된다.

 

2. 임베디드 타입(복합 값 타입)

임베디드 타입이란?

  • JPA에서 새로운 값 타입을 직접 정의할 수 있음
  • 여러 개의 기본 값 타입을 하나의 객체로 묶어 사용
  • @Embeddable + @Embedded를 사용하여 선언
  • 예시: 주소(Address), 근무 기간(Period)

임베디드 타입 적용 예제

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;
}
@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    private String name;

    @Embedded  // 임베디드 값 타입 사용
    private Address homeAddress;
}

장점

  • 재사용 가능
  • 응집력 증가 (관련 필드를 하나의 객체로 묶어 관리)
  • 엔티티를 더 세밀하게 설계 가능

 

3. 값 타입과 불변 객체

값 타입은 불변(immutable) 객체로 만들어야 안전하다.
값 타입을 여러 엔티티에서 공유하면 부작용(side effect)이 발생할 수 있기 때문이다.

값 타입 공유의 문제

Member member1 = new Member();
member1.setHomeAddress(new Address("서울", "강남", "12345"));

Member member2 = new Member();
member2.setHomeAddress(member1.getHomeAddress()); // 같은 주소를 공유

member2.getHomeAddress().setCity("부산"); // member1도 부산으로 변경됨

 

해결 방법: 값 타입을 불변 객체로 설계

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

    // 생성자로만 값 설정 (Setter 제거)
    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}

불변 객체를 사용하면 값이 변경될 일이 없으므로 부작용이 사라진다.

 

4. 값 타입의 비교

값 타입은 값이 같으면 같은 것으로 간주해야 한다.

비교 방식

  1. 동일성(identity) 비교: == (참조 비교)
  2. 동등성(equivalence) 비교: equals() (값 비교)

값 타입 비교 예제

Address a = new Address("서울");
Address b = new Address("서울");

System.out.println(a == b);        // false (참조 다름)
System.out.println(a.equals(b));   // true (값이 같음)

값 타입을 올바르게 비교하려면 equals() 메서드를 재정의해야 한다.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Address address = (Address) o;
    return city.equals(address.city) &&
           street.equals(address.street) &&
           zipcode.equals(address.zipcode);
}

 

5. 값 타입 컬렉션

값 타입을 컬렉션으로 저장하려면 @ElementCollection을 사용해야 한다.

값 타입 컬렉션 매핑

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    
    private String name;

    @ElementCollection
    @CollectionTable(name = "address", joinColumns = @JoinColumn(name = "member_id"))
    private List<Address> addresses = new ArrayList<>();
}

값 타입 컬렉션의 특징

  • 엔티티가 삭제되면 컬렉션도 함께 삭제됨
  • 지연 로딩(Lazy Loading) 적용
  • 값 변경 시 기존 데이터를 삭제하고 새로운 데이터를 삽입함

 

6. 정리

엔티티 타입 vs 값 타입

  • 엔티티 타입: @Entity 사용, 식별자 존재, 생명주기 관리 가능
  • 값 타입: 식별자 없음, 불변 객체로 사용하는 것이 안전

값 타입 활용 방법

  • 임베디드 타입(@Embedded): 관련 데이터를 하나의 객체로 묶어서 사용
  • 값 타입 컬렉션(@ElementCollection): 컬렉션 형태로 저장 (하지만 변경 시 전체 삭제 후 재삽입됨)
  • 불변 객체로 설계하여 데이터 일관성을 유지

값 타입은 단순한 데이터를 다룰 때만 사용하고, 지속적인 추적이 필요한 경우 엔티티를 사용해야 한다.

반응형