Study/JPA

[JPA] @ElementCollection, @CollectionTable, 값 타입 컬렉션 설정

lsh2613 2023. 9. 12. 22:38

값 타입 컬렉션

따로 @entity를 선언하지 않고 @ElementCollection, @CollectionTable을 통해서 간단하게 엔티티 테이블을 만들 수 있다.

 

먼저 Member에서 여러 개의 Address를 가질 수 있을 때 다음과 같이 적용할 수 있다.

 

@Embeddable
public class Address {
    String city;

    public Address() {
    }

    public Address(String city) {
        this.city = city;
    }
}
@Entity
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @ElementCollection
    @CollectionTable(name = "ADDRESS",
            joinColumns = @JoinColumn(name = "MEMBER_ID"))
    @Column(name="CITY")
    private List<Address> addressHistory = new ArrayList<Address>();
    
    ...
}

설명하자면 Member 엔티티는 Embedded 타입의 Address를 여러 개 가질 수 있어 List로 관리하고 있다. 이때 매핑되는 Address 테이블은 ADDRESS의 이름을 갖고 Member_ID를 외래키로 가진다.

 

@Column은 매핑되는 Address 클래스의 필드가 하나만 존재하기 때문에 이 필드에 대한 name을 지정해줄 수 있다. 필드가 여러 개라면 사용할 수 없다.

 

따라서 다음과 같이 두 테이블이 만들어진다.

 

값 타입 컬렉션을 사용해보자.

Member member = new Member();
List<Address> addressHistory = member.getAddressHistory();

addressHistory.add(new Address("강남"));
addressHistory.add(new Address("판교"));
addressHistory.add(new Address("잠실"));

em.persist(member);

tx.commit();

값 타입 컬렉션을 조회할 때 페치 전략을 선택할 수 있는데 디폴트는 LAZY이고 영속성 전이(Cascade) + 고아 객체 제거(Orphan Remove) 기능을 필수로 가지고 있다.

 

이렇게 되게 간단해 보이는 값 타입 컬렉션을 알아보았지만 사실 아주 큰 단점이 하나 존재한다.

제약사항

이미 눈치 챘을 수도 있지만 값 타입 컬렉션으로 만들어진 엔티티 테이블은 Member처럼 Long 타입의 대체키가 아닌 MEMBER_ID, CITY 두 필드를 기본키로 가지고 있다.

 

식별자를 가지고 있으면 엔티티 값을 변경하더라도 식별자를 통해 DB에 저장된 원본 데이터를 쉽게 찾아 변경할 수 있다.

반면에 위에서 선언한 Address 처럼 값 타입(정확히는 Embedded 타입으로 값 타입 중 하나)은 식별자라는 개념이 없고 단순한 값들의 모음이므로 값을 변경해 버리면 DB에 저장된 원본 데이터를 찾기힘들다.

 

이러한 문제로 JPA 구현체들은 값 타입 컬렉션에 변경 사항이 발생하면, 값 타입 컬렉션이 매핑된 테이블의 연관된 모든 데이터를 삭제하고, 현재 값 타입 컬렉션 객체에 있는 모든 값을 DB에 다시 저장한다.

 

따라서 실무에서는 값 타입 컬렉션이 매핑된 테이블에 데이터가 많다면 값 타입 컬렉션 대신에 일대다 관계로 매핑해야 한다.

 

당연히 일대다 관계로 대신하기 위해선 값 타입 컬렉션이 갖는 특징을 가질 수 있도록 영속성 전이 + 고아 객체 제거를 추가로 구현해주어야 하고 모든 컬럼을 묶어 기본키로 설정해주어야 한다.