Backend study/JPA

값 타입

adulty22 2024. 11. 1. 15:49

JPA에서 값 타입(Value Type)은 엔티티와는 달리 식별자가 없는 데이터 타입으로, 데이터의 속성 값이나 속성 집합을 표현하는 데 사용된다. 값 타입은 엔티티의 속성을 구성하거나 다른 엔티티와 관계 없는 데이터를 표현할 때 주로 사용된다.

JPA에서 값 타입은 크게 다음 네 가지로 분류된다:

  1. 기본 값 타입
  2. 임베디드 타입(복합 값 타입)
  3. 값 타입과 불변 객체
  4. 값 타입 컬렉션

 

1. 기본 값 타입 (Basic Value Type)

기본 값 타입은 자바에서 기본적으로 제공하는 기본 데이터 타입래퍼 클래스, 그리고 String, BigDecimal, BigInteger 같은 타입들을 의미한다. 기본 값 타입은 자체적으로 값이 변경되거나 분리될 수 없는 타입이다.

기본 값 타입의 특징

  • 식별자가 없음: 기본 값 타입은 고유 식별자를 가지지 않으며, 단순히 값으로만 존재한다.
  • 생명 주기 독립적: 값 타입의 생명 주기는 그 값을 포함하고 있는 엔티티에 종속되며, 독립적으로 존재할 수 없다.
  • 참조 공유 없음: 값 타입의 객체는 다른 엔티티와 참조로 공유되지 않으며 독립적인 값으로만 사용된다.

예시: 기본 값 타입 사용

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

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

위 예시에서 name과 age는 각각 String과 int 타입의 기본 값 타입으로, 식별자가 없고 독립적으로 관리된다.

 

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

임베디드 타입(복합 값 타입)은 여러 속성을 하나의 클래스에 묶어 엔티티의 속성으로 사용하는 값 타입이다. 임베디드 타입을 사용하면 엔티티의 속성을 논리적으로 분리하고 재사용할 수 있다. 예를 들어, 주소 정보(city, street, zipcode)를 한 클래스로 묶어 사용하면 여러 엔티티에서 재사용할 수 있다.

임베디드 타입 정의

  1. @Embeddable: 임베디드 타입으로 사용할 클래스를 정의할 때 사용한다.
  2. @Embedded: 엔티티에서 임베디드 타입을 속성으로 사용할 때 선언한다.

예시: 임베디드 타입 사용

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

    // 기본 생성자, getter, setter, equals, hashCode
}

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

    private String name;

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

임베디드 타입의 특징

  • 논리적 그룹화: Address처럼 여러 필드를 하나의 객체로 묶어 엔티티의 속성을 논리적으로 분리할 수 있다.
  • 재사용성: Address 객체를 여러 엔티티에서 재사용할 수 있어 중복 코드를 줄이고 유지보수를 쉽게 한다.
  • 내장된 생명 주기: 임베디드 타입은 부모 엔티티와 생명 주기를 공유하며, 독립적으로 관리되지 않는다.

임베디드 타입 속성 오버라이딩

@AttributeOverrides와 @AttributeOverride를 통해 임베디드 타입의 속성 이름을 오버라이딩할 수 있다.

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

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "city", column = @Column(name = "shipping_city")),
        @AttributeOverride(name = "street", column = @Column(name = "shipping_street")),
        @AttributeOverride(name = "zipcode", column = @Column(name = "shipping_zipcode"))
    })
    private Address shippingAddress;
}

 

3. 값 타입과 불변 객체 (Immutable Object)

JPA에서 값 타입은 불변 객체로 설계하는 것이 중요하다. 불변 객체는 한 번 생성된 후 값이 변하지 않는 객체를 의미하며, 값 타입이 불변 객체가 되면 데이터의 일관성을 보장할 수 있다.

값 타입을 불변 객체로 설계하는 방법

  1. Setter 제공하지 않기: 값 타입 객체는 생성 이후 변경이 없도록 Setter 메서드를 제공하지 않는다.
  2. 생성자에서 모든 값 초기화: 필수 값은 모두 생성자를 통해 설정하고 이후에는 변경되지 않도록 한다.
  3. 컬렉션을 사용하지 않기: 값 타입 필드에 컬렉션을 사용하면 컬렉션 내부의 요소가 변경될 수 있으므로, 대신 값 타입 컬렉션을 활용하는 것이 좋다.

불변 객체의 장점

  • 데이터 일관성 보장: 불변 객체는 한 번 설정된 값을 변경할 수 없으므로 데이터의 안정성을 보장한다.
  • 안전한 공유: 여러 엔티티에서 동일한 값 타입을 공유할 때 값이 변경되지 않아 참조를 안전하게 공유할 수 있다.
@Embeddable
public class Address {
    private final String city;
    private final String street;
    private final String zipcode;

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

    // Getter만 제공하여 불변 객체로 유지
}

 

4. 값 타입의 비교

값 타입의 비교는 식별자가 없는 값을 비교할 때 중요하다. 값 타입을 비교할 때는 동일성(==) 비교 대신 동등성(equals()) 비교를 사용한다. 값 타입의 동등성 비교를 위해 equals()와 hashCode() 메서드를 올바르게 구현하는 것이 중요하다.

값 타입 비교 예시

Address address1 = new Address("Seoul", "Street 1", "12345");
Address address2 = new Address("Seoul", "Street 1", "12345");

System.out.println(address1.equals(address2)); // true, equals()로 비교
System.out.println(address1 == address2); // false, 객체 주소값 비교

위 코드에서 address1과 address2는 내용은 같지만 다른 인스턴스이므로 == 비교는 false를 반환하고, equals() 비교는 true를 반환한다.

 

5. 값 타입 컬렉션

값 타입 컬렉션은 값 타입을 리스트, 세트, 맵 등의 컬렉션 형태로 사용하는 경우를 의미한다. 예를 들어, 하나의 회원이 여러 전화번호를 가질 수 있다면 List<String> 같은 값 타입 컬렉션을 사용할 수 있다.

값 타입 컬렉션 특징

  • 생명 주기 공유: 값 타입 컬렉션의 생명 주기는 소유하는 엔티티에 종속된다.
  • 컬렉션 변경 시 모든 요소 삭제 후 재삽입: 값 타입 컬렉션의 요소를 변경하면 전체를 삭제하고 다시 추가하는 방식으로 동작한다.
  • 고아 객체 제거: 값 타입 컬렉션의 요소는 다른 엔티티에서 공유할 수 없으며, 부모 엔티티가 삭제되면 값 타입 컬렉션 요소도 함께 삭제된다.

값 타입 컬렉션 사용 예시

@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;

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

@ElementCollection과 @CollectionTable

  • @ElementCollection: 값 타입 컬렉션을 지정하는 어노테이션이다. List, Set, Map과 함께 사용할 수 있다.
  • @CollectionTable: 값 타입 컬렉션을 저장하는 별도의 테이블을 지정한다. joinColumns를 통해 외래 키를 매핑한다.

값 타입 컬렉션의 주의점

  • 쓰기 시 성능 주의: 값 타입 컬렉션을 수정할 때는 기존 요소를 모두 삭제하고 새로 삽입하므로, 빈번한 업데이트가 발생하는 경우 성능에 영향을 줄 수 있다.
  • 고아 객체 관리: 값 타입 컬렉션은 고아 객체 관리가 자동으로 적용되므로, 컬렉션에서 요소를 제거하면 해당 요소는 자동으로 삭제된다.

 

  • 기본 값 타입: 자바의 기본 데이터 타입 및 String, BigDecimal 같은 타입을 의미한다. 값 타입은 식별자가 없고 독립적인 생명 주기가 없다.
  • 임베디드 타입(복합 값 타입): 여러 속성을 하나의 값 타입으로 묶어 사용할 수 있으며, 엔티티에서 독립된 객체로 존재할 수 없다.
  • 값 타입과 불변 객체: 값 타입을 불변 객체로 설계하여 데이터의 일관성을 유지하고 참조 공유 시 안전성을 보장할 수 있다.
  • 값 타입 비교: 값 타입의 동등성 비교는 equals() 메서드를 재정의하여 비교하는 것이 중요하다.
  • 값 타입 컬렉션: 값 타입을 컬렉션으로 사용하는 방법으로, 부모 엔티티의 생명 주기에 종속되고, 고아 객체 관리가 자동으로 적용된다.
728x90