값 타입을 컬렉션에 담아서 사용하는 것을 의미한다
값 타입을 하나 이상 저장할 때 사용한다
영속성 전이(CASCADE) + 고아 객체 제거 기능을 가지는 느낌
코드)
public class Member {
...
@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD", joinColumns =
@JoinColumn(name = "MEMBER_ID")) // 테이블 이름 지정
@Column(name = "FOOD_NAME")
private Set<String> favoriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name = "ADDRESS", joinColumns =
@JoinColumn(name = "MEMBER_ID")) // 테이블 이름 지정
private List<Address> addressHistory = new ArrayList<>();
...
}
Member member = new Member();
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("피자");
member.getFavoriteFoods().add("족발");
member.getAddressHistory().add(new Address("old1", "street", "10000"));
member.getAddressHistory().add(new Address("old2", "street", "10000"));
em.persist(member);
System.out.println("=======START========");
Member findMember = em.find(Member.class, 1L);
지연로딩
이기 때문에, 조회되지 않는다.실제로 사용될 때, DB로 쿼리가 날라간다.
System.out.println("=======START========");
Member findMember = em.find(Member.class, 1L);
List<Address> addressHistory = findMember.getAddressHistory();
for (Address address : addressHistory) {
System.out.println("address = " + address.getCity());
}
Set<String> favoriteFoods = findMember.getFavoriteFoods();
for (String favoriteFood : favoriteFoods) {
System.out.println("favoriteFood = " + favoriteFood);
}
System.out.println("=======START========");
Member findMember = em.find(Member.class, member.getId());
// 치킨 -> 한식, 아예 지우고 새로운 값을 넣어야 한다. (수정할 수 있는 개념이 아님)
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");
// 먼저 기존 객체를 지워야 한다. 주의할 것은 Address에 equals(), hashcode() 메소드가 재정의되어 있어야 함!! 안되어있으면 삭제가 안 됨
findMember.getAddressHistory().remove(new Address("old1", "street", "10000")); // 기존 객체
findMember.getAddressHistory().add(new Address("newCity1", "street", "10000"));
불변
이어야 하므로, 수정해서 사용하는 것이 아니라 아예 새로운 것으로 대체되어야 한다ADDRESS 부분
쿼리를 잘 보면, ADDRESS 테이블 싹 비웠다가 다시 데이터를 넣는 것을 확인할 수 있다.
값 타입 컬렉션에 변경사항이 발생하면, 주인 엔티티(Member)와 연관된 모든 데이터를 삭제하고, 값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
@Entity
class AddressEntity {
@Id @GeneratedValue
private Long id;
private Address address;
public AddressEntity() {}
public AddressEntity(Address address) {
this.address = address;
}
public AddressEntity(String city, String street, String zipcode) {
this.address = new Address(city, street, zipcode);
}
}
@Entity
class Member {
...
// 값 타입 매핑에서 엔티티 매핑으로 변경
//@ElementCollection
//@CollectionTable(name = "ADDRESS", joinColumns =
// @JoinColumn(name = "MEMBER_ID"))
//private List<Address> addressHistory = new ArrayList<>();
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();
...
}
값 타입 -> 엔티티 타입으로 변경 후, 데이터 삽입
findMember.getAddressHistory().add(new AddressEntity("old1", "street", "10000"));
findMember.getAddressHistory().add(new AddressEntity("newCity1", "street", "10000"));
진짜 단순할 때 쓴다
추적할 필요가 없고, 값이 바뀌어도 업데이트할 필요가 없을 때
값 타입은 정말 값 타입이라 판단될 때만 사용한다
식별자가 필요하고, 지속해서 값을 추적/변경해야 한다면 그건 값 타입이 아닌 엔티티 타입이다!