값 타입을 컬렉션으로 넣어서 쓰는 것
을 의미@ElementCollection
, @CollectionTable
사용@ElementCollection
: 컬렉션 객체임을 JPA가 알 수 있게 해준다@CollectionTable
: 값 타입 컬렉션을 매핑할 테이블에 대한 역할을 지정하는 데 사용한다.별도의 테이블
이 필요좋아하는 음식들을 저장하는 favoriteFoods 주소내역을 저장하는 addressHistory 구현해보자.
...
//favoriteFoods
//값 타입 컬렉션 지정
@ElementCollection
@CollectionTable(name="FAVORITE_FOOD", joinColumns =
@JoinColumn(name="MEMBER_ID")) //조인할 컬럼명 지정
@Column(name="FOOD_NAME") //컬럼명 지정
private Set<String> favoriteFoods = new HashSet<>();
//addressHistory
//값 타입 컬렉션 지정
@ElementCollection
@CollectionTable(name="ADDRESS", joinColumns =
@JoinColumn(name="MEMBER_ID"))
private List<Address> addressHistory = new ArrayList<>();
@ElementCollection
: 컬렉션 객체임을 JPA가 알 수 있게 해준다. 엔티티가 아닌 값 타입, 임베디드 타입에 대한 테이블을 생성하고 1대다 관계로 다룬다.@CollectionTable
: 값 타입 컬렉션을 매핑할 테이블에 대한 역할을 지정하는 데 사용한다. 테이블의 이름과 조인정보를 적어줘야 한다.Member member1 = new Member();
member1.setUsername("member1");
member1.setHomeAddress(new Address("city1","Street1", "1000"));
member1.getFavoriteFoods().add("치킨");
member1.getFavoriteFoods().add("피자");
member1.getFavoriteFoods().add("족발");
member1.getAddressHistory().add(new Address("old1","Street1", "1000"));
member1.getAddressHistory().add(new Address("old2","Street2", "1000"));
//멤버만 persist
em.persist(member1);
//멤버만 불러와짐. 컬렉션타입 : 기본값은 지연로딩
System.out.println("==================== START ====================");
Member findMember = em.find(Member.class, member1.getId());
//setter 사용X. 사이드이펙트 생길수 있다.
//findMember.getHomeAddress().setCity("newCity");
// 치킨 -> 한식
member.getFavoriteFoods().remove("치킨");
member.getFavoriteFoods().add("한식");
// 주소변경
member.getAddressHistory().remove(new Address("oldCity", "street", "10000"));
member.getAddressHistory().add(new Address("newCity", "street", "10000"));
데이터를 삭제 후 변경된 데이터 전체를 새로 추가
해줘야 한다.//값 타입 2개 add
member.setAddressHistory.add(new Address("oldCity1", "street", "10000");
member.setAddressHistory.add(new Address("oldCity2", "street", "10000");
member.getAddressHistory().remove(new Address("oldCity1", "street", "10000"));
member.getAddressHistory().add(new Address("newCity", "street", "10000"));
- 값 타입 컬렉션에 Address 데이터가 2개 저장되어 있는 상황에서 기존에 있던 데이터 1개를 삭제하고 새로운 데이터를 추가해 주었다.
- 우리가 기대하는 SQL은 delete 쿼리 1개와 insert 쿼리 1개이다.
- 하지만 값 타입 컬렉션은 주인 엔티티와 연관된 모든 데이터를 삭제하고 다시 저장하기 때문에 Member에 연관된 oldCity1, oldCity2를 모두 삭제하고 oldCity2와 newCity를 insert 한다.
➡️따라서 delete 쿼리 1개와 insert 쿼리 2개가 DB로 나가게 된다.
영속성 전이(Cascade) + 고아 객체 제거
를 사용해서 값 타입 컬렉션 처럼 사용@Entity
public class AddressEntity {
@Id @GeneratedValue
private Long id;
private Address address;
public AddressEntity(String city, String street, String zipcode ) {
this.address = new Address(city, street, zipcode);
}
public AddressEntity() {
}
}
@Entity
public class Member {
...
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "MEMBER_ID")
private List<AddressEntity> addressHistory = new ArrayList<>();
...
}
- Address라는 값 타입을 감싸는 AddressEntity를 새롭게 만들고 저장하는 방식
- cascade를 ALL, orphanRemoval을 true로 설정해 주면 주인 엔티티에 의존하면서 기존의 제약사항을 해결할 수 있다.