ElementCollection

  • JPA에서 값타입을 컬렉션에 담아서 사용하는 경우, 해당 컬렉션을 ElemenctCollection 이라고 한다.
  • 여기서 값타입이란 Integer, String과 같은 java의 기본 자료형 또는 임베디드 타입을 의미한다.
  • 현재 진행 중인 프로젝트에서 매장(Store)의 휴무일(offDays)을 ElementCollection으로 사용하고 있다.
@Entity
public class Store extends BaseEntity {
    
    // ...
   
	@ElementCollection(fetch = FetchType.LAZY)  // default: LAZY라 사실 명시할 필요없음
    private List<String> offDays = new ArrayList<>();
 
    // ...
}
  • 위처럼 @ElementCollection 애노테이션만 붙여주면 해당 필드가 Collection이라는 것을 알게 되고, RDB에서 이 필드를 관리할 별도의 테이블을 생성해준다.
  • 함께 @CollectionTable을 사용하여 매핑할 테이블의 정보를 직접 설정해줄 수도 있다.
  • 보다싶이 애노테이션 하나 혹은 둘 만으로 RDB가 다루지 못하는 Colleciton 형태의 데이터를 쉽게 처리할 수 있게 도와주는 편리한 기능이다.

  • 하지만 ElementCollection은 문제점을 몇가지 가지고 있고, 나 또한 이를 사용하면서 해당 문제들로 인한 성능 개선의 필요성을 크게 느꼈기에 ElementCollection을 대체하고자 한다.


ElemenctCollection의 한계

1. 엔티티가 값타입이기 때문에 식별자 개념이 없다.

  • 식별자가 존재하지 않기 때문에 값이 변경되는 경우, 이를 추적하는 것이 어렵다.
  • 사실 이 문제점으로 인한 에로사항은 크게 느끼지 못하였다. (추적한 적이 아직 없음)
  • 하지만 식별자의 부재로 인한 문제점은 겪어보지 않아도 충분히 와닿는다.

2. 식별자가 별도로 없으므로 PK를 구성하는 경우 조건에 맞게 모든 컬럼을 묶어서 PK를 구성해야 한다.

  • 값타입을 저장할 @Collection 테이블의 구성을 보면 아래와 같다.

collection table

  • 테이블의 구성을 보면 앞서 언급한 1번의 문제대로 식별자가 존재하지 않고, 값타입을 가지고 있는 Store 엔티티의 id값 (PK)와 offDays의 값만 존재한다.
  • 결국 식별자가 없기에 해당 데이터의 값이 변경되면 어떤 레코드가 변경됐는지 추적하기가 어렵다.
  • 또한 PK의 조건 (Not null, Unique)을 갖추려면 id와 off_days 두 컬럼을 묶어야만 가능하므로 PK를 생성할 때 복합키 방식을 강제로 사용하여야 한다.

3. 변경 사항이 발생하면 연관 테이블의 데이터를 모두 삭제하고 남아있거나 새로 추가된 데이터를 다시 넣는다

  • 무엇보다 가장 직관적이게 느껴지는 문제점은 3번 문제점인 것 같다.
  • 만약 위 테이블에서 36번 Store의 offDays를 변경하는 경우, update 쿼리가 하나 날라가거나 더 많은 offDays가 들어온 경우 insert 쿼리가 함께 날라가는 것을 생각하게 된다.
  • 하지만 현실은 delete 쿼리가 날라간 후, 기존의 데이터와 함께 값으로 들어온 모든 데이터를 insert 쿼리로 입력한다.

elementcollection 수정

elementcollection 수정

  • 위처럼 delete 쿼리로 값타입이 저장된 테이블의 모든 데이터를 날린 후, insert 쿼리가 추가된 데이터의 수만큼 반복하여 날라간다.
    • ex) 기존 화요일 + 수요일, 목요일인 경우 => delete후 (화, 수, 목) insert 쿼리가 총 3번 날라감.
  • 이는 전혀 예상치 못한 방향으로 DB가 동작하고 있는 것이다.


@ManyToMany로 대체하자

  • 따라서 값타입 자체를 엔티티로 생각하고 데이터를 처리하는 방식으로 변환하기로 마음을 먹었다.
  • 강의나 기타 블로그를 보면 일대다 관계를 고려하라고 하는데, 현재 내 기능의 경우 다대다의 관계가 필요하므로 이를 사용할 것이다.
  • 물론 @ManyToMany 에노테이션을 그대로 사용하는 것이 아닌 @OneToMany, @ManyToOne 관계로 풀어내어 사용한다.

변경된 엔티티 관계

  • 기존의 Collection 테이블이 아닌 엔티티간의 연관 관계로 이를 풀어냈다.
@Entity
public class Store extends BaseEntity {
 	// ...
    // Days (N:N)
    @OneToMany(mappedBy = "store")
    private List<DaysOfStore> daysOfStoreList = new ArrayList<>();
    
    // ...
}

@Entity
public class DaysOfStore {
    @Id
    @GeneratedValue
    @Column(name = "days_of_store_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "days_id")
    private Days days;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "store_id")
    private Store store;
    
    // ...
}

@Entity
public class Days {

    @Id
    @GeneratedValue
    @Column(name = "days_id")
    private Long id;

    @Enumerated(EnumType.STRING)
    private DaysType days;

    @OneToMany(mappedBy = "days")
    private List<DaysOfStore> daysOfStoreList = new ArrayList<>();
 
    // ...
}


  • @ManyToMany를 DaysOfStore 엔티티를 활용하여 @OneToMany, @ManyToOne 관계로 풀어주었다.
  • 이로인해 겪게될 문제점과 해결 방법은 다음 포스팅에서.

댓글남기기