db에는 컬렉션을 저장할 수 없습니다. 따라서 jpa의 값 타입 컬렉션은 @ElementCollection과 @CollectionTable 어노테이션을 통해 구현할 수 있습니다. 이번 글에서는 이 어노테이션들을 활용한 값 타입 컬렉션의 개념과 특징, 수정에 대해 알아보겠습니다.
목차
1. 값 타입 컬렉션의 개념과 특징
2. 값 타입 컬렉션의 수정
값 타입 컬렉션의 개념과 특징
1. 개념
값 타입을 컬렉션에 담아 사용하는 것을 의미합니다.DB에서는 따로 컬렉션을 저장할 수 없으므로, 컬렉션에 해당하는 테이블을 하나 추가하여 컬렉션을 구현합니다. 이를 위해 @ElementCollection과 @CollectionTable 어노테이션을 사용합니다.
2. 특징
① 값 타입 컬렉션은 값 타입과 마찬가지로, 따로 생명주기를 가지지 않고 엔티티와 같은 생명주기를 갖습니다. 일대다 관계에서 CASCADE = ALL, orphanREmoval = TRUE를 설정해준 것과 같습니다. 아래의 예를 통해 이해해보겠습니다.
Member.class
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@ElementCollection
@CollectionTable(name = "ADDRESS",
joinColumns = @JoinColumn(name = "MEMBER_ID") )
private List<Address> addressList = new ArrayList<>();
}
getter setter가 있다고 가정합니다.
실행.class
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setUsername("member1");
member.getAddressList().add(new Address("city1", "street1", "1"));
member.getAddressList().add(new Address("city2", "street2", "2"));
em.persist(member);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
아래는 실행 결과입니다. 따로 값 타입 컬렉션을 persist해주지 않았음에도 insert쿼리가 작동하는 것을 알 수 있습니다.
② 지연로딩전략을 사용합니다.
try {
Member member = new Member();
member.setUsername("member1");
member.getAddressList().add(new Address("city1", "street1", "1"));
member.getAddressList().add(new Address("city2", "street2", "2"));
em.persist(member);
em.flush();
em.clear();
System.out.println("================== START ================");
Member foundMember = em.find(Member.class, member.getId());
System.out.println("================== 지연로딩 ================");
List<Address> addressList = foundMember.getAddressList();
for (Address address : addressList) {
System.out.println("address.getCity() = " + address.getCity());
}
tx.commit();
member를 find할 때 바로 값 타입 컬렉션을 꺼내오는 것이 아니라, 필요한 순간에 지연로딩 됩니다. 아래의 결과를 보고 이해할 수 있습니다.
값 타입 컬렉션의 수정
remove 후 add 하는 방식
값 타입 컬렉션은 call by reference 이므로 값만 단순히 수정해줄 수 없습니다. 따라서 remove후 add하는 방식으로 값 타입을 수정할 수 있지만 독특한 부분이 있습니다. update로 원하는 부분만 수정해주는 것이아니라, delete로 모두 삭제하고 insert 쿼리가 나가기 때문입니다.
코드 예시
Member member = new Member();
member.setUsername("member1");
member.getAddressList().add(new Address("city1", "street1", "1"));
member.getAddressList().add(new Address("city2", "street2", "2"));
em.persist(member);
Member foundMember = em.find(Member.class, member.getId());
foundMember.getAddressList().remove(new Address("city1", "street1", "1"));
foundMember.getAddressList().add(new Address("newCity1", "street1", "1"));
tx.commit();
이유
1. 값 타입은 엔티티와 다르게 식별자 개념이 없습니다. 값은 변경하면 추적이 어렵습니다. JPA에서는 값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 관련된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장하게 됩니다.
값 타입 컬렉션의 대안
실무에서는 상황에 따라 값 타입 컬렉션 대신 컬렉션을 Entity로 승격하고 일대다 관계를 고려할 수 있습니다. 아래의 코드와 비교해보면 좋을 것같습니다.
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressEntityList = new ArrayList<>();
'백엔드 > JPA' 카테고리의 다른 글
[JPA] N+1 문제를 해결하기 위한 fetch (0) | 2023.03.06 |
---|---|
[JPA] 기본 값 타입과 임베디드(embedded) 타입 (0) | 2022.08.19 |
[JPA] 연관관계 주인이 필요한 이유 (0) | 2022.08.11 |
[JPA] 영속성 컨텍스트 정리 (0) | 2022.08.05 |