✅ @ElementCollection ✅ @CollectionTable
✅ 실무에서는 값 타입 컬렉션 대신에 일대다 관계를 고려한다. (대신 영속성 전이(Cascade) + 고아 객체 제거 사용)
데이터베이스는 컬렉션을 같은 테이블에 저장할 수 없다.
👉 따라서 컬렉션을 저장하기 위한 별도의 테이블이 필요하다.
영속성 전이 (Cascade) + 고아 객체 제거 기능을 필수로 가진다.
[JPA] 영속성 전이 (CASCADE), 고아 객체
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
@Embedded
private Address homeAddress;
@ElementCollection![](https://velog.velcdn.com/images/peanut_/post/32047e8e-000b-4f0a-bb3f-497788ae198e/image.png)
@CollectionTable(name = "FAVORITE_FOOD", joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME")
private Set<String> faveriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name = "ADDRESS", joinColumns = @JoinColumn(name = "MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "street", "10000"));
member.getFaveriteFoods().add("치킨");
member.getFaveriteFoods().add("족발");
member.getFaveriteFoods().add("피자");
member.getAddressHistory().add(new Address("old1", "street", "10000"));
member.getAddressHistory().add(new Address("old2", "street", "10000"));
em.persist(member);
값 타입 컬렉션도 지연 로딩 전략 사용
[JPA] 즉시 로딩(EAGER) vs 지연 로딩(LAZY)
// <저장>
Member member = new Member();
member.setUsername("member1");
member.setHomeAddress(new Address("homeCity", "street", "10000"));
member.getFaveriteFoods().add("치킨");
member.getFaveriteFoods().add("족발");
member.getFaveriteFoods().add("피자");
member.getAddressHistory().add(new Address("old1", "street", "10000"));
member.getAddressHistory().add(new Address("old2", "street", "10000"));
em.persist(member);
em.flush();
em.clear();
// <조회>
Member findMember = em.find(Member.class, member.getId());
// <수정>
// findMember.getHomeAddress().setCity("newCity); -> 불변 객체이므로 불가능
Address a = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", a.getStreet(), a.getZipcode()));
findMember.getFaveriteFoods().remove("치킨");
findMember.getFaveriteFoods().add("한식");
remove()를 사용할때 equals, hashCode를 사용하는데, 삭제하고자 하는 값 타입 컬렉션에 맞게 equals와 hashCode가 오버라이드 되어 있지 않으면 삭제 되지 않는 상황이 생기기도 함
// ...(위 코드와 동일)...
findMember.getAddressHistory().remove(new Address("old1", "street", "10000"));
findMember.getAddressHistory().add(new Address("newCity1", "street", "10000"));
👉 old1이 삭제되지 않음
@Override
public boolean equals(Object o){
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
Address address = (Address) o;
return Objects.equals(city, address.city) &&
Objects.equals(street, address.street) &&
Objects.equals(zipcode, address.zipcode);
}
@Override
public int hashCode(){
return Objects.hash(city, street, zipcode);
👉 old1이 정상적으로 삭제됨
- 여러 불편함이 있기 때문에 실무에서는 값 타입 컬렉션 대신에 일대다 관계를 고려한다.
- 대신 영속성 전이(Cascade) + 고아 객체 제거를 사용해서 값 타입 컬 렉션처럼 사용한다.
- 정말 단순한 경우, 값을 추적할 필요가 없을 경우에만 선택적으로 값 타입을 사용한다.
@Entity
public class AddressEntity {
@Id
@GeneratedValue
private Long id;
private Address address;
}
// 값 타입으로 매핑
// @ElementCollection
// @CollectionTable(name = "ADDRESS", joinColumns = @JoinColumn(name = "MEMBER_ID"))
// private List<Address> addressHistory = new ArrayList<>();
// 엔티티로 매핑
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) // 일대다 관계로 매핑(영속성 전이(Cascade) + 고아 객체 제거)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();