값 타입 컬렉션
미리 결론 스포, 값 타입 컬렉션은 언제 쓰는가?
=> 매우 단순한 상황일때만 쓰자!(라고 하기도 좀 그렇고, 쓰는 걸 고려하자 정도가 맞다.)
=> 대부분 다 엔티티로 만들어서 사용한다.
값 타입 컬렉션
- 값 타입을 컬렉션에 담아서 쓰는 것
그림
예제
Member 클래스 변경
@Entity
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@Embedded
private Address address;
@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME") // 얘는 값이 String 하나고 내가 정의한 게 아니므로. 예외적 허용.
private Set<String> favoriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name = "ADDRESS",
joinColumns = @JoinColumn(name = "MEMBER_ID")) // 이러면 얘를 외래키로 잡게 됨.
private List<Address> addressHistory = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
실행하면
이러한 형태(유의깊게 봐야할 부분)
값 타입 컬렉션 사용
1. 값 타입 저장 예제
Member에 새로 만든 필드들에 getter, setter를 추가해주고
<전체>
@Entity
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@Embedded
private Address homeAddress;
@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME") // 얘는 값이 String 하나고 내가 정의한 게 아니므로. 예외적 허용.
private Set<String> favoriteFoods = new HashSet<>();
@ElementCollection
@CollectionTable(name = "ADDRESS",
joinColumns = @JoinColumn(name = "MEMBER_ID")) // 이러면 얘를 외래키로 잡게 됨.
private List<Address> addressHistory = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Address getHomeAddress() {
return homeAddress;
}
public void setHomeAddress(Address homeAddress) {
this.homeAddress = homeAddress;
}
public Set<String> getFavoriteFoods() {
return favoriteFoods;
}
public void setFavoriteFoods(Set<String> favoriteFoods) {
this.favoriteFoods = favoriteFoods;
}
public List<Address> getAddressHistory() {
return addressHistory;
}
public void setAddressHistory(List<Address> addressHistory) {
this.addressHistory = addressHistory;
}
}
JpaMain에도 저장을 시켜주는 코드를 작성한다.
<전체>
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.setHomeAddress(new Address("homeCity", "street", "10000"));
//이렇게 하면 Member 테이블에 값들이 들어갈 거예요.
// FAVORITEFOODS 컬렉션에 아래 세 가지가 들어간다.
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
member.getAddressHistory().add(new Address("oldCity2", "street", "10000"));
em.persist(member); // 저장 (하나만 함)
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
실행 결과
18:58:30.056 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert inflearn.exjpa.jpaExample.Member
*/ insert
into
Member
(city, street, zipcode, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?)
18:58:30.060 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Inserting collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
18:58:30.060 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.addressHistory */ insert
into
ADDRESS
(MEMBER_ID, city, street, zipcode)
values
(?, ?, ?, ?)
18:58:30.061 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.addressHistory */ insert
into
ADDRESS
(MEMBER_ID, city, street, zipcode)
values
(?, ?, ?, ?)
18:58:30.063 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Inserting collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
18:58:30.063 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert
into
FAVORITE_FOOD
(MEMBER_ID, FOOD_NAME)
values
(?, ?)
18:58:30.064 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert
into
FAVORITE_FOOD
(MEMBER_ID, FOOD_NAME)
values
(?, ?)
18:58:30.064 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert
into
FAVORITE_FOOD
(MEMBER_ID, FOOD_NAME)
values
(?, ?)
설명
1.
Hibernate:
/* insert inflearn.exjpa.jpaExample.Member
*/ insert
into
Member
(city, street, zipcode, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?)
여기서 이 3개는
member.setHomeAddress(new Address("homeCity", "street", "10000")); 이거다.
2.
addressHistory를 두 번 insert 함.
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.addressHistory */ insert
into
ADDRESS
(MEMBER_ID, city, street, zipcode)
values
(?, ?, ?, ?)
18:58:30.061 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.addressHistory */ insert
into
ADDRESS
(MEMBER_ID, city, street, zipcode)
values
(?, ?, ?, ?)
이거는
member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
member.getAddressHistory().add(new Address("oldCity2", "street", "10000"));
이거 때문이다.
3.
favoriteFoods는 3번 insert 된다.
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert
into
FAVORITE_FOOD
(MEMBER_ID, FOOD_NAME)
values
(?, ?)
18:58:30.064 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert
into
FAVORITE_FOOD
(MEMBER_ID, FOOD_NAME)
values
(?, ?)
18:58:30.064 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert
into
FAVORITE_FOOD
(MEMBER_ID, FOOD_NAME)
values
(?, ?)
H2 결과 확인해보면
문제 없이 잘 들어가 있다.
흥미로운 부분은,
값 타입 컬렉션을 지금 따로 persist 하지 않았음.
그런데 member만 하니까 값 타입 컬렉션들이 자동으로 들어갔다.
homeAddress는 소속된 임베디드 값 타입이니까 당연히 들어간거고
컬렉션도 다른 테이블임에도 불구하고 라이프사이클이 같이 돌아간 것.
-> 값 타입 컬렉션 때문에 그렇다.
값 타입 컬렉션은 본인 스스로의 라이프사이클이 없다. 모든 생명주기가 Member에 소속되어 있는 것.
값 타입 컬렉션도 값 타입이므로 전부 Member에 의존.
그래서 Member에서 값을 바꾸면 자동으로 변경됨.
2. 값 타입 조회 예제
- 값 타입 컬렉션도 지연 로딩 전략 사용!
JPAMain 아래에다가
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.setHomeAddress(new Address("homeCity", "street", "10000"));
//Member 테이블에 값들이 들어갈 거예요.
// 이 FAVORITEFOODS 컬렉션에 세 가지가 들어간다.
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
member.getAddressHistory().add(new Address("oldCity2", "street", "10000"));
em.persist(member); // 저장
em.flush();
em.clear();
System.out.println("============START============");
Member findMember = em.find(Member.class, member.getId());
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
em.flush();
em.clear();
System.out.println("============START============");
Member findMember = em.find(Member.class, member.getId());
이거 추가하고 일단 실행시켜보면
============START============
19:46:41.075 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
member0_.MEMBER_ID as member_i1_6_0_,
member0_.city as city2_6_0_,
member0_.street as street3_6_0_,
member0_.zipcode as zipcode4_6_0_,
member0_.USERNAME as username5_6_0_
from
Member member0_
where
member0_.MEMBER_ID=?
이렇게 나온다.
지금 내가 Member만 조회했으니 member만 가지고 오는 상황.
이를 통해 값 타입 컬렉션도 지연 로딩이다는 것을 확인 가능.
그런데 또 재밌는 것은
@Embedded
private Address homeAddress;
는 Member에 소속된 값 타입이기 때문에 같이 조회가 된다.
member0_.city as city2_6_0_,
member0_.street as street3_6_0_,
member0_.zipcode as zipcode4_6_0_,
얘는 굳이 지연로딩 할 필요가 없지.
그래서 굳이 찍어줘야 쿼리가 나갈 것이다.
추가한 부분 아래
List<Address> addresssHistory = findMember.getAddressHistory();
for (Address address : addresssHistory) { // iter 라고만 쓰면 알아서 향상된 for문 나옴.
System.out.println("address = " + address.getCity());
}
Set<String> favoriteFoods = findMember.getFavoriteFoods();
for (String favoriteFood : favoriteFoods) {
System.out.println("favoriteFood = " + favoriteFood);
}
그냥 전체적 코드 한 번 더 쓰면
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.setHomeAddress(new Address("homeCity", "street", "10000")); //Member 테이블에 값들이 들어갈 거예요.
// 이 FAVORITEFOODS 컬렉션에 세 가지가 들어간다.
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
member.getAddressHistory().add(new Address("oldCity2", "street", "10000"));
em.persist(member); // 저장
em.flush();
em.clear();
System.out.println("============START============");
Member findMember = em.find(Member.class, member.getId());
// 추가한 부분, 이제서야 쿼리 나갈 것이다.
List<Address> addresssHistory = findMember.getAddressHistory();
for (Address address : addresssHistory) { // iter 라고만 쓰면 알아서 향상된 for문 나옴.
System.out.println("address = " + address.getCity());
}
Set<String> favoriteFoods = findMember.getFavoriteFoods();
for (String favoriteFood : favoriteFoods) {
System.out.println("favoriteFood = " + favoriteFood);
}
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
실행해보면
============START============
19:57:34.860 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
member0_.MEMBER_ID as member_i1_6_0_,
member0_.city as city2_6_0_,
member0_.street as street3_6_0_,
member0_.zipcode as zipcode4_6_0_,
member0_.USERNAME as username5_6_0_
from
Member member0_
where
member0_.MEMBER_ID=?
19:57:34.868 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
addresshis0_.MEMBER_ID as member_i1_0_0_,
addresshis0_.city as city2_0_0_,
addresshis0_.street as street3_0_0_,
addresshis0_.zipcode as zipcode4_0_0_
from
ADDRESS addresshis0_
where
addresshis0_.MEMBER_ID=?
19:57:34.869 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl - Preparing collection initializer : [inflearn.exjpa.jpaExample.Member.addressHistory#1]
19:57:34.871 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
19:57:34.872 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
19:57:34.872 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: inflearn.exjpa.jpaExample.Member.addressHistory
19:57:34.872 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
19:57:34.872 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: inflearn.exjpa.jpaExample.Member.addressHistory
19:57:34.872 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection
address = oldCity1
address = oldCity2
19:57:34.872 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
favoritefo0_.MEMBER_ID as member_i1_4_0_,
favoritefo0_.FOOD_NAME as food_nam2_4_0_
from
FAVORITE_FOOD favoritefo0_
where
favoritefo0_.MEMBER_ID=?
19:57:34.873 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl - Preparing collection initializer : [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
19:57:34.873 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
19:57:34.873 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
19:57:34.873 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
19:57:34.873 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
19:57:34.873 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
19:57:34.873 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
19:57:34.873 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection
favoriteFood = 족발
favoriteFood = 치킨
favoriteFood = 피자
후략
종료 코드 0(으)로 완료된 프로세스
지연 로딩임을 확인할 수 있음.
3. 값 타입 수정 예제
값을 좀 바꿔서
물론 먼저, 이건 컬렉션 내용은 아니다.
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.setHomeAddress(new Address("homeCity", "street", "10000")); //Member 테이블에 값들이 들어갈 거예요.
// 이 FAVORITEFOODS 컬렉션에 세 가지가 들어간다.
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
member.getAddressHistory().add(new Address("oldCity2", "street", "10000"));
em.persist(member); // 저장
em.flush();
em.clear();
System.out.println("============START============");
Member findMember = em.find(Member.class, member.getId());
// iter지우고 수정 예제
//이렇게 하면 일단 안 된다. sideEffect 발생
// findMember.getHomeAddress().setCity("newCity");
// 답은, 아예 새로 넣기다.(통으로 값 타입을 갈아끼운다. 완전한 교체
// 도시명만 바꾸고 나머지는 그대로 하고싶다고 가정.
Address origin = findMember.getHomeAddress();
findMember.setHomeAddress(new Address("newCity", origin.getStreet(), origin.getZipcode()));
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
실행하면
============START============
20:42:06.226 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
member0_.MEMBER_ID as member_i1_6_0_,
member0_.city as city2_6_0_,
member0_.street as street3_6_0_,
member0_.zipcode as zipcode4_6_0_,
member0_.USERNAME as username5_6_0_
from
Member member0_
where
member0_.MEMBER_ID=?
중략
20:42:06.240 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* update
inflearn.exjpa.jpaExample.Member */ update
Member
set
city=?,
street=?,
zipcode=?,
USERNAME=?
where
MEMBER_ID=?
조회해 온 다음에 업데이트 쿼리 나감을 확인할 수 있음.
이제 컬렉션을 수정해보면
// 이제 값 타입 컬렉션 업데이트
// 컬렉션 내의 치킨 => 힌식으로 변경
findMember.getFavoriteFoods().remove("치킨");
findMember.getFavoriteFoods().add("한식");
아예 지우고, 새로운 것을 넣어라.
실행 결과를 보면
21:03:25.894 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* delete collection row inflearn.exjpa.jpaExample.Member.favoriteFoods */ delete
from
FAVORITE_FOOD
where
MEMBER_ID=?
and FOOD_NAME=?
21:03:25.894 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Done deleting collection rows: 1 deleted
// 지우고
// 집어넣는다.
21:03:25.894 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Inserting rows of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:03:25.895 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.favoriteFoods */ insert
into
FAVORITE_FOOD
(MEMBER_ID, FOOD_NAME)
values
(?, ?)
컬렉션의 값만 변경해도 실제 데잍베이스 쿼리가 날아가면서 뭐가 변경되었는지 알고 JPA가 알아서 바꿔준다.
마치 영속성 전이 처럼.
이제 주소 바꾸기
실행하면
============START============
중략 군데군데 중략
21:13:35.032 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl - Preparing collection initializer : [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.033 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.033 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.033 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.033 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
21:13:35.034 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.034 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
21:13:35.034 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection
21:13:35.034 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Loading collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.034 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
addresshis0_.MEMBER_ID as member_i1_0_0_,
addresshis0_.city as city2_0_0_,
addresshis0_.street as street3_0_0_,
addresshis0_.zipcode as zipcode4_0_0_
from
ADDRESS addresshis0_
where
addresshis0_.MEMBER_ID=?
21:13:35.034 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl - Preparing collection initializer : [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.035 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.035 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.035 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: inflearn.exjpa.jpaExample.Member.addressHistory
21:13:35.035 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.035 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: inflearn.exjpa.jpaExample.Member.addressHistory
21:13:35.035 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection
21:13:35.035 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - committing
21:13:35.035 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
21:13:35.035 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
21:13:35.035 [main] DEBUG org.hibernate.engine.spi.CollectionEntry - Collection dirty: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.035 [main] DEBUG org.hibernate.engine.spi.CollectionEntry - Collection dirty: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
21:13:35.035 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.addressHistory#1], was: [inflearn.exjpa.jpaExample.Member.addressHistory#1] (initialized)
21:13:35.035 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1], was: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1] (initialized)
21:13:35.035 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects
21:13:35.035 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 (re)creations, 2 updates, 0 removals to 2 collections
21:13:35.035 [main] DEBUG org.hibernate.internal.util.EntityPrinter - Listing entities:
21:13:35.036 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.jpaExample.Member{id=1, addressHistory=[component[city,street,zipcode]{zipcode=10000, city=oldCity2, street=street}, component[city,street,zipcode]{zipcode=10000, city=newCity1, street=street}], favoriteFoods=[족발, 한식, 피자], homeAddress=component[city,street,zipcode]{zipcode=10000, city=newCity, street=street}, username=member1}
21:13:35.036 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* update
inflearn.exjpa.jpaExample.Member */ update
Member
set
city=?,
street=?,
zipcode=?,
USERNAME=?
where
MEMBER_ID=?
21:13:35.039 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Deleting collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.039 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* delete collection inflearn.exjpa.jpaExample.Member.addressHistory */ delete
from
ADDRESS
where
MEMBER_ID=?
21:13:35.040 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Done deleting collection
21:13:35.040 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Inserting collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
21:13:35.040 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.addressHistory */ insert
into
ADDRESS
(MEMBER_ID, city, street, zipcode)
values
(?, ?, ?, ?)
21:13:35.041 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert collection
row inflearn.exjpa.jpaExample.Member.addressHistory */ insert
into
ADDRESS
(MEMBER_ID, city, street, zipcode)
values
(?, ?, ?, ?)
Member에 소속된 데이터 중에 ADRESS를 다 지움.
그리고 insert 쿼리가 두 번 날아가는데, remove로 oldCity1을 날렸어도, oldCity2가 남아있으니까.
두 개를 다 완전히 갈아끼워 버린 것. oldCity1과 newCity1을 다시 각각 인서트하므로 쿼리 두 번 날아감.
값 타입 컬렉션의 제약사항
@OrderColumn 어노테이션을 쓰기도 하는데, 허점이 너무 많다.
********* 값 타입 컬렉션의 대안 (매우 중요)
예제 - AddressEntity 사용
AddressEntity 생성
@Entity
@Table(name = "ADDRESS")
@NoArgsConstructor
@AllArgsConstructor
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 Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Member 변경(수정한 부분 그냥 주석처리)
@Entity
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@Embedded
private Address homeAddress;
@ElementCollection
@CollectionTable(name = "FAVORITE_FOOD",
joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "FOOD_NAME") // 얘는 값이 String 하나고 내가 정의한 게 아니므로. 예외적 허용.
private Set<String> favoriteFoods = new HashSet<>();
// @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<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Address getHomeAddress() {
return homeAddress;
}
public void setHomeAddress(Address homeAddress) {
this.homeAddress = homeAddress;
}
public Set<String> getFavoriteFoods() {
return favoriteFoods;
}
public void setFavoriteFoods(Set<String> favoriteFoods) {
this.favoriteFoods = favoriteFoods;
}
// public List<Address> getAddressHistory() {
// return addressHistory;
// }
//
// public void setAddressHistory(List<Address> addressHistory) {
// this.addressHistory = addressHistory;
// }
public List<AddressEntity> getAddressHistory() {
return addressHistory;
}
public void setAddressHistory(List<AddressEntity> addressHistory) {
this.addressHistory = addressHistory;
}
}
JPAMain도 코드 변경
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.setHomeAddress(new Address("homeCity", "street", "10000"));
//Member 테이블에 값들이 들어갈 거예요.
// 이 FAVORITEFOODS 컬렉션에 세 가지가 들어간다.
member.getFavoriteFoods().add("치킨");
member.getFavoriteFoods().add("족발");
member.getFavoriteFoods().add("피자");
// member.getAddressHistory().add(new Address("oldCity1", "street", "10000"));
// member.getAddressHistory().add(new Address("oldCity2", "street", "10000"));
// 이렇게 바꾼다. 위를 아래로
member.getAddressHistory().add(new AddressEntity("oldCity1", "street", "10000"));
em.persist(member); // 저장
em.flush();
em.clear();
System.out.println("============START============");
Member findMember = em.find(Member.class, member.getId());
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
실행 결과 보면
필요한 것만 따왔음.
일대다로 해서 저장 잘 됨.
22:07:42.605 [main] DEBUG org.hibernate.SQL -
Hibernate:
create table ADDRESS (
id bigint not null,
city varchar(255),
street varchar(255),
zipcode varchar(255),
MEMBER_ID bigint,
primary key (id)
)
22:07:42.608 [main] DEBUG org.hibernate.SQL -
Hibernate:
create table Member (
MEMBER_ID bigint not null,
city varchar(255),
street varchar(255),
zipcode varchar(255),
USERNAME varchar(255),
primary key (MEMBER_ID)
)
Hibernate:
alter table ADDRESS
add constraint FKsuulxb5rmrxvb83yr43ox86wn
foreign key (MEMBER_ID)
references Member
22:07:42.616 [main] DEBUG org.hibernate.SQL -
22:07:42.639 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - No JtaPlatform was specified, checking resolver
22:07:42.639 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformResolverInitiator - No JtaPlatformResolver was specified, using default [org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver]
22:07:42.642 [main] DEBUG org.hibernate.engine.transaction.jta.platform.internal.StandardJtaPlatformResolver - Could not resolve JtaPlatform, using default [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
22:07:42.642 [main] INFO org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator - HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
22:07:42.645 [main] DEBUG org.hibernate.service.internal.SessionFactoryServiceRegistryImpl - EventListenerRegistry access via ServiceRegistry is deprecated. Use `sessionFactory.getEventEngine().getListenerRegistry()` instead
22:07:42.646 [main] DEBUG org.hibernate.hql.internal.QueryTranslatorFactoryInitiator - QueryTranslatorFactory: org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory@45cd7bc5
22:07:42.648 [main] DEBUG org.hibernate.query.spi.NamedQueryRepository - Checking 0 named HQL queries
22:07:42.649 [main] DEBUG org.hibernate.query.spi.NamedQueryRepository - Checking 0 named SQL queries
22:07:42.649 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Initializing SessionFactoryRegistry : org.hibernate.internal.SessionFactoryRegistry@2e09c51
22:07:42.651 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Registering SessionFactory: 6dd942d0-cb20-4769-9f1d-f5d00c8de59d (<unnamed>)
22:07:42.651 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Not binding SessionFactory to JNDI, no JNDI name configured
22:07:42.691 [main] DEBUG org.hibernate.stat.internal.StatisticsInitiator - Statistics initialized [enabled=false]
22:07:42.696 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
22:07:42.696 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - begin
22:07:42.700 [main] DEBUG org.hibernate.SQL -
call next value for hibernate_sequence
Hibernate:
call next value for hibernate_sequence
22:07:42.704 [main] DEBUG org.hibernate.id.enhanced.SequenceStructure - Sequence value obtained: 1
22:07:42.704 [main] DEBUG org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl - HHH000387: ResultSet's statement was not registered
22:07:42.706 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Generated identifier: 1, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
22:07:42.725 [main] DEBUG org.hibernate.SQL -
call next value for hibernate_sequence
Hibernate:
call next value for hibernate_sequence
22:07:42.726 [main] DEBUG org.hibernate.id.enhanced.SequenceStructure - Sequence value obtained: 2
22:07:42.726 [main] DEBUG org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl - HHH000387: ResultSet's statement was not registered
22:07:42.726 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Generated identifier: 2, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
22:07:42.727 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
22:07:42.728 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
22:07:42.731 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.addressHistory#1], was: [<unreferenced>] (initialized)
22:07:42.732 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1], was: [<unreferenced>] (initialized)
22:07:42.733 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 2 insertions, 0 updates, 0 deletions to 2 objects
22:07:42.733 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 2 (re)creations, 0 updates, 0 removals to 2 collections
22:07:42.734 [main] DEBUG org.hibernate.internal.util.EntityPrinter - Listing entities:
22:07:42.734 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.jpaExample.Member{id=1, addressHistory=[inflearn.exjpa.jpaExample.AddressEntity#2], favoriteFoods=[족발, 치킨, 피자], homeAddress=component[city,street,zipcode]{zipcode=10000, city=homeCity, street=street}, username=member1}
22:07:42.734 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.jpaExample.AddressEntity{address=component[city,street,zipcode]{zipcode=10000, city=oldCity1, street=street}, id=2}
22:07:42.737 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert inflearn.exjpa.jpaExample.Member
*/ insert
into
Member
(city, street, zipcode, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?)
22:07:42.741 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert inflearn.exjpa.jpaExample.AddressEntity
*/ insert
into
ADDRESS
(city, street, zipcode, id)
values
(?, ?, ?, ?)
22:07:42.744 [main] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister - Inserting collection: [inflearn.exjpa.jpaExample.Member.addressHistory#1]
22:07:42.744 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* create one-to-many row inflearn.exjpa.jpaExample.Member.addressHistory */ update
ADDRESS
set
MEMBER_ID=?
where
id=?
============START============
22:07:42.758 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
member0_.MEMBER_ID as member_i1_6_0_,
member0_.city as city2_6_0_,
member0_.street as street3_6_0_,
member0_.zipcode as zipcode4_6_0_,
member0_.USERNAME as username5_6_0_
from
Member member0_
where
member0_.MEMBER_ID=?
22:07:42.762 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl - On call to EntityIdentifierReaderImpl#resolve, EntityKey was already known; should only happen on root returns with an optional identifier specified
22:07:42.766 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving attributes for [inflearn.exjpa.jpaExample.Member#1]
22:07:42.766 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `addressHistory` : value = NOT NULL COLLECTION
22:07:42.766 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`addressHistory`) - enhanced for lazy-loading? - false
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `favoriteFoods` : value = NOT NULL COLLECTION
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`favoriteFoods`) - enhanced for lazy-loading? - false
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `homeAddress` : value = [Ljava.lang.Object;@65fe2691
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`homeAddress`) - enhanced for lazy-loading? - false
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `username` : value = member1
22:07:42.767 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`username`) - enhanced for lazy-loading? - false
22:07:42.768 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [inflearn.exjpa.jpaExample.Member#1]
22:07:42.769 [main] DEBUG org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader - Done entity load : inflearn.exjpa.jpaExample.Member#1
22:07:42.770 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Loading collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.770 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
favoritefo0_.MEMBER_ID as member_i1_4_0_,
favoritefo0_.FOOD_NAME as food_nam2_4_0_
from
FAVORITE_FOOD favoritefo0_
where
favoritefo0_.MEMBER_ID=?
22:07:42.771 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl - Preparing collection initializer : [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.773 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.773 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.773 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.773 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
22:07:42.773 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.773 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: inflearn.exjpa.jpaExample.Member.favoriteFoods
22:07:42.773 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection
22:07:42.773 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - committing
22:07:42.773 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Processing flush-time cascades
22:07:42.773 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Dirty checking collections
22:07:42.773 [main] DEBUG org.hibernate.engine.spi.CollectionEntry - Collection dirty: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1]
22:07:42.774 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.addressHistory#1], was: [inflearn.exjpa.jpaExample.Member.addressHistory#1] (uninitialized)
22:07:42.774 [main] DEBUG org.hibernate.engine.internal.Collections - Collection found: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1], was: [inflearn.exjpa.jpaExample.Member.favoriteFoods#1] (initialized)
22:07:42.774 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 insertions, 1 updates, 0 deletions to 1 objects
22:07:42.774 [main] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener - Flushed: 0 (re)creations, 1 updates, 0 removals to 2 collections
22:07:42.774 [main] DEBUG org.hibernate.internal.util.EntityPrinter - Listing entities:
22:07:42.774 [main] DEBUG org.hibernate.internal.util.EntityPrinter - inflearn.exjpa.jpaExample.Member{id=1, addressHistory=<uninitialized>, favoriteFoods=[족발, 한식, 피자], homeAddress=component[city,street,zipcode]{zipcode=10000, city=newCity, street=street}, username=member1}
22:07:42.774 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* update
inflearn.exjpa.jpaExample.Member */ update
Member
set
city=?,
street=?,
zipcode=?,
USERNAME=?
where
MEMBER_ID=?
그런데 일대다 다방향 매핑이라서 업데이트 쿼리 나가는 건 어쩔 수가 없다.
다른 테이블에 외래키가 있기 때문에.
그래서 데이터베이스 가서 확인하고 오면
여기 지금 ID 개념이 생겼다.
즉, 값 타입이 아니라 엔티티라는 것.
이렇게 되면 값을 가져와서 수정하는 것을 맘대로 할 수 있게 된다.
값 타입을 엔티티로 승급시킴.
정리
값 타입은 정말 값 타입이라 판단될 때만 사용하라
엔티티와 값 타입을 혼동해서 엔티티를 값 타입으로 만들면 안 됨
식별자가 필요하고, 지속해서 값을 추적, 변경해야 한다면
그것은 값 타입이 아닌 엔티티