Member를 조회할 때 Team도 함께 조회해야 할까?
실습
Member와 Team을 조회해서 가져오려고 하면
그러면 어느 경우에는 Team까지 함께 가져오고, 어떨 때는 Member만 가져오는 쿼리를 날릴 수 있을까에 대한 고민
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 = em.find(Member.class, 1L);
printMemberAndTeam(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
private static void printMemberAndTeam(Member member) {
String username = member.getUsername();
System.out.println("username = " + username);
Team team = member.getTeam();
System.out.println("team = " + team.getName());
}
}
Member member = em.find(Member.class, 1L); 여기서 쿼리가 나갈 때 한번에 다 가져오면 좋죠.
그럼 출력할 때 이걸 다 출력할 수 있다.
만약 바뀌어서 Member만 출력하면 된다면
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 = em.find(Member.class, 1L);
printMember(member);
// printMemberAndTeam(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
private static void printMember(Member member) {
System.out.println("member = " + member.getUsername());
}
private static void printMemberAndTeam(Member member) {
String username = member.getUsername();
System.out.println("username = " + username);
Team team = member.getTeam();
System.out.println("team = " + team.getName());
}
}
사용하지 않는데 Team까지 가져온다면 손해.
JPA는 이걸 프록시와 관련된 지연 로딩으로 기가막히게 해결.
실습 먼저
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("hello");
em.persist(member);
em.flush();
em.clear();
//
Member findMember = em.find(Member.class, member.getId());
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " +findMember.getUsername());
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
이렇게 하고 실행해도
Hibernate:
select
member0_.MEMBER_ID as member_i1_3_0_,
member0_.createdBy as createdb2_3_0_,
member0_.createdDate as createdd3_3_0_,
member0_.lastModifiedBy as lastmodi4_3_0_,
member0_.lastModifiedDate as lastmodi5_3_0_,
member0_.TEAM_ID as team_id7_3_0_,
member0_.USERNAME as username6_3_0_,
team1_.TEAM_ID as team_id1_7_1_,
team1_.createdBy as createdb2_7_1_,
team1_.createdDate as createdd3_7_1_,
team1_.lastModifiedBy as lastmodi4_7_1_,
team1_.lastModifiedDate as lastmodi5_7_1_,
team1_.name as name6_7_1_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.TEAM_ID
where
member0_.MEMBER_ID=?
21:56:33.091 [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
21:56:33.094 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving attributes for [inflearn.exjpa.jpaExample.Member#1]
21:56:33.094 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `createdBy` : value = null
21:56:33.094 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`createdBy`) - enhanced for lazy-loading? - false
21:56:33.094 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `createdDate` : value = null
21:56:33.094 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`createdDate`) - enhanced for lazy-loading? - false
21:56:33.094 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `lastModifiedBy` : value = null
21:56:33.094 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`lastModifiedBy`) - enhanced for lazy-loading? - false
21:56:33.094 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `lastModifiedDate` : value = null
21:56:33.095 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`lastModifiedDate`) - enhanced for lazy-loading? - false
21:56:33.095 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `memberProducts` : value = NOT NULL COLLECTION
21:56:33.095 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`memberProducts`) - enhanced for lazy-loading? - false
21:56:33.095 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `team` : value = null
21:56:33.095 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`team`) - enhanced for lazy-loading? - false
21:56:33.095 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `username` : value = hello
21:56:33.095 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`username`) - enhanced for lazy-loading? - false
21:56:33.096 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [inflearn.exjpa.jpaExample.Member#1]
21:56:33.096 [main] DEBUG org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader - Done entity load : inflearn.exjpa.jpaExample.Member#1
findMember.id = 1
findMember.username = hello
id랑 username 잘 나옴
그런데 만약에 getReference()로 바꾼다
// Member findMember = em.find(Member.class, member.getId());
Member findMember = em.getReference(Member.class, member.getId()); // getReference로 변경함.
// System.out.println("findMember.id = " + findMember.getId());
// System.out.println("findMember.username = " +findMember.getUsername());
이러면 select 쿼리가 아무 것도 안 나감.
그런데 아래 두 줄도 주석을 풀어서 출력을 해본다면
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("hello");
em.persist(member);
em.flush();
em.clear();
//
// Member findMember = em.find(Member.class, member.getId());
Member findMember = em.getReference(Member.class, member.getId()); // getReference로 변경함.
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " +findMember.getUsername());
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
쿼리 확인해보면
22:10:18.657 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert inflearn.exjpa.jpaExample.Member
*/ insert
into
Member
(createdBy, createdDate, lastModifiedBy, lastModifiedDate, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?)
findMember.id = 1
22:10:18.673 [main] DEBUG org.hibernate.internal.SessionImpl - Initializing proxy: [inflearn.exjpa.jpaExample.Member#1]
22:10:18.676 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
member0_.MEMBER_ID as member_i1_3_0_,
member0_.createdBy as createdb2_3_0_,
member0_.createdDate as createdd3_3_0_,
member0_.lastModifiedBy as lastmodi4_3_0_,
member0_.lastModifiedDate as lastmodi5_3_0_,
member0_.TEAM_ID as team_id7_3_0_,
member0_.USERNAME as username6_3_0_,
team1_.TEAM_ID as team_id1_7_1_,
team1_.createdBy as createdb2_7_1_,
team1_.createdDate as createdd3_7_1_,
team1_.lastModifiedBy as lastmodi4_7_1_,
team1_.lastModifiedDate as lastmodi5_7_1_,
team1_.name as name6_7_1_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.TEAM_ID
where
member0_.MEMBER_ID=?
22:10:18.680 [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:10:18.684 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving attributes for [inflearn.exjpa.jpaExample.Member#1]
22:10:18.684 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `createdBy` : value = null
22:10:18.684 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`createdBy`) - enhanced for lazy-loading? - false
22:10:18.684 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `createdDate` : value = null
22:10:18.684 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`createdDate`) - enhanced for lazy-loading? - false
22:10:18.684 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `lastModifiedBy` : value = null
22:10:18.684 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`lastModifiedBy`) - enhanced for lazy-loading? - false
22:10:18.684 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `lastModifiedDate` : value = null
22:10:18.684 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`lastModifiedDate`) - enhanced for lazy-loading? - false
22:10:18.684 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `memberProducts` : value = NOT NULL COLLECTION
22:10:18.684 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`memberProducts`) - enhanced for lazy-loading? - false
22:10:18.685 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `team` : value = null
22:10:18.685 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`team`) - enhanced for lazy-loading? - false
22:10:18.685 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `username` : value = hello
22:10:18.685 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`username`) - enhanced for lazy-loading? - false
22:10:18.686 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [inflearn.exjpa.jpaExample.Member#1]
22:10:18.687 [main] DEBUG org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader - Done entity load : inflearn.exjpa.jpaExample.Member#1
findMember.username = hello
select 쿼리 나갔다.
설명하자면
Member findMember = em.getReference(Member.class, member.getId()); 이걸 호출하는 시점에는 데이터베이스에 쿼리가 안 나감.
id는 파라미터로 넣어줬기 때문에 DB까지 안 가도 알 수 있는데,
findMember.getUsername(); 이거는 DB에 있는 값이다.
그래서 JPA가 getUsername();을 호출하는 시점에 DB에 쿼리를 날린다.
그래서 findMember의 값을 채운 다음에 username을 출력한다.
그런데 그럼, findMember가 도대체 뭔가에 대한 의문이 생겨 이것까지 찍어보면
Member findMember = em.getReference(Member.class, member.getId()); // getReference로 변경함.
System.out.println("findMember = " + findMember.getClass());
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.username = " + findMember.getUsername());
로그를 보면
22:23:51.477 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert inflearn.exjpa.jpaExample.Member
*/ insert
into
Member
(createdBy, createdDate, lastModifiedBy, lastModifiedDate, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?)
findMember = class inflearn.exjpa.jpaExample.Member$HibernateProxy$YfVxrFz5
findMember.id = 1
22:23:51.494 [main] DEBUG org.hibernate.internal.SessionImpl - Initializing proxy: [inflearn.exjpa.jpaExample.Member#1]
22:23:51.498 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
member0_.MEMBER_ID as member_i1_3_0_,
member0_.createdBy as createdb2_3_0_,
member0_.createdDate as createdd3_3_0_,
member0_.lastModifiedBy as lastmodi4_3_0_,
member0_.lastModifiedDate as lastmodi5_3_0_,
member0_.TEAM_ID as team_id7_3_0_,
member0_.USERNAME as username6_3_0_,
team1_.TEAM_ID as team_id1_7_1_,
team1_.createdBy as createdb2_7_1_,
team1_.createdDate as createdd3_7_1_,
team1_.lastModifiedBy as lastmodi4_7_1_,
team1_.lastModifiedDate as lastmodi5_7_1_,
team1_.name as name6_7_1_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.TEAM_ID
where
member0_.MEMBER_ID=?
22:23:51.503 [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:23:51.507 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving attributes for [inflearn.exjpa.jpaExample.Member#1]
22:23:51.507 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `createdBy` : value = null
22:23:51.507 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`createdBy`) - enhanced for lazy-loading? - false
22:23:51.507 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `createdDate` : value = null
22:23:51.507 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`createdDate`) - enhanced for lazy-loading? - false
22:23:51.507 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `lastModifiedBy` : value = null
22:23:51.507 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`lastModifiedBy`) - enhanced for lazy-loading? - false
22:23:51.507 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `lastModifiedDate` : value = null
22:23:51.507 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`lastModifiedDate`) - enhanced for lazy-loading? - false
22:23:51.507 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `memberProducts` : value = NOT NULL COLLECTION
22:23:51.507 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`memberProducts`) - enhanced for lazy-loading? - false
22:23:51.508 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `team` : value = null
22:23:51.509 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`team`) - enhanced for lazy-loading? - false
22:23:51.509 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `username` : value = hello
22:23:51.509 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`username`) - enhanced for lazy-loading? - false
22:23:51.509 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [inflearn.exjpa.jpaExample.Member#1]
22:23:51.510 [main] DEBUG org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader - Done entity load : inflearn.exjpa.jpaExample.Member#1
findMember.username = hello
findMember = class inflearn.exjpa.jpaExample.Member$HibernateProxy$YfVxrFz5
이게 나오는데
Member가 아니라 뒤에 사족이 붙어있다.
하이버네이트가 만든 가짜 클래스라는 의미
프록시 클래스이다.
개념 설명
프록시 기초
프록시 특징
** 프록시 객체의 초기화
** 프록시의 특징
아래부터 쭉 예시이다.
프록시 객체가 실제 엔티티로 바뀌는 것은 아님 예시
before / after 찍어보면
findMember.id = 1
before findMember = class inflearn.exjpa.jpaExample.Member$HibernateProxy$Di5hnncr
23:30:59.553 [main] DEBUG org.hibernate.internal.SessionImpl - Initializing proxy: [inflearn.exjpa.jpaExample.Member#1]
23:30:59.556 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
member0_.MEMBER_ID as member_i1_3_0_,
member0_.createdBy as createdb2_3_0_,
member0_.createdDate as createdd3_3_0_,
member0_.lastModifiedBy as lastmodi4_3_0_,
member0_.lastModifiedDate as lastmodi5_3_0_,
member0_.TEAM_ID as team_id7_3_0_,
member0_.USERNAME as username6_3_0_,
team1_.TEAM_ID as team_id1_7_1_,
team1_.createdBy as createdb2_7_1_,
team1_.createdDate as createdd3_7_1_,
team1_.lastModifiedBy as lastmodi4_7_1_,
team1_.lastModifiedDate as lastmodi5_7_1_,
team1_.name as name6_7_1_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.TEAM_ID
where
member0_.MEMBER_ID=?
23:30:59.560 [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
23:30:59.564 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving attributes for [inflearn.exjpa.jpaExample.Member#1]
23:30:59.564 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `createdBy` : value = null
23:30:59.564 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`createdBy`) - enhanced for lazy-loading? - false
23:30:59.564 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `createdDate` : value = null
23:30:59.564 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`createdDate`) - enhanced for lazy-loading? - false
23:30:59.564 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `lastModifiedBy` : value = null
23:30:59.564 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`lastModifiedBy`) - enhanced for lazy-loading? - false
23:30:59.564 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `lastModifiedDate` : value = null
23:30:59.564 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`lastModifiedDate`) - enhanced for lazy-loading? - false
23:30:59.564 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `memberProducts` : value = NOT NULL COLLECTION
23:30:59.564 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`memberProducts`) - enhanced for lazy-loading? - false
23:30:59.565 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `team` : value = null
23:30:59.565 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`team`) - enhanced for lazy-loading? - false
23:30:59.565 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Processing attribute `username` : value = hello
23:30:59.565 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Attribute (`username`) - enhanced for lazy-loading? - false
23:30:59.565 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [inflearn.exjpa.jpaExample.Member#1]
23:30:59.567 [main] DEBUG org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader - Done entity load : inflearn.exjpa.jpaExample.Member#1
findMember.username = hello
after findMember = class inflearn.exjpa.jpaExample.Member$HibernateProxy$Di5hnncr
before findMember = class inflearn.exjpa.jpaExample.Member$HibernateProxy$Di5hnncr
findMember.username = hello
after findMember = class inflearn.exjpa.jpaExample.Member$HibernateProxy$Di5hnncr
이렇게 똑같이 나오는 걸 확인 가능
타입 체크시 주의해야함 예시
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 member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
em.persist(member2);
em.flush();
em.clear();
Member fm1 = em.find(Member.class, member1.getId());
Member fm2 = em.find(Member.class, member2.getId());
System.out.println("fm1 == fm2 " + (fm1.getClass() == fm2.getClass()));
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
이렇게 바꾸고 찍어보면
이게 출력됨.
그런데 이걸 바꿔서
Member fm1 = em.find(Member.class, member1.getId());
Member fm2 = em.getReference(Member.class, member2.getId());
System.out.println("fm1 == fm2 의 비교 값 : " + (fm1.getClass() == fm2.getClass()));
이렇게 해버리면
이렇게 출력된다.
타입의== 비교이기 때문에
실제 비즈니스 로직에서는
타입을 비교할 때 메서드만 가지고 판단해야한다.
이게 실제 객체로 넘어올 지, 프록시로 넘어올지는 모른다!
그래서 절대 타입 비료를 == 로 하면 안 된다.
그래서 이걸 instanceof 로 비교 방법을 바꿔야 한다.
다시 고친 버전
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 member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
em.persist(member2);
em.flush();
em.clear();
Member fm1 = em.find(Member.class, member1.getId());
Member fm2 = em.getReference(Member.class, member2.getId());
logic(fm1, fm2); // 메서드로 분리
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
private static void logic(Member fm1, Member fm2) {
System.out.println("fm1 == fm2 : " + (fm1 instanceof Member));
System.out.println("fm1 == fm2 : " + (fm2 instanceof Member));
}
}
둘 다 true로 나옴.
이미 있으면 em.getReference() 호출해도 실제 엔티티 반환 예시
수정해서
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 member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
em.flush();
em.clear();
Member findM1 = em.find(Member.class, member1.getId());
System.out.println("findM1 = " + findM1.getClass());
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
클래스가 정확하게 나온다.
프록시가 아니라 진짜 멤버 타입이므로
영속성 컨텍스트에 이미 올라가 있으므로.
여기서 만약에 그 아래에 추가해서
Member findM1 = em.find(Member.class, member1.getId());
System.out.println("findM1 = " + findM1.getClass());
// 여기다가 추가
Member referenceM = em.getReference(Member.class, member1.getId());
System.out.println("referenceM = " + referenceM.getClass());
이렇게 만들어서 출력 결과를 보면
똑같음을 확인할 수 있음.
힌트 :JPA에서는 같은 트랜잭션 안에서 == 비교를 조회하면 항상 같다고 나온다.
이유
1) 이미 멤버를 1차 캐시에 올려놨는데 그것을 굳이 프록시로 가져와봤자 아무 의미가 없다.
2) JPA에서는 이러한 비교는 항상 true가 나온다.
System.out.println("a == a : " + (findM1 == referenceM));
findM1 이 실제든 프록시든 상관 없이, 마치 자바 컬렉션에서 가져온 것을 == 비교 하듯이,이 == 비교가, 같은 영속성 컨텍스트에서 가져온 것이고, PK가 똑같으면 JPA에서는 항상 true를 반환해줘야 한다. (규칙 - 동일한 트랜잭션 내에서 보장해줌)=> JPA가 기본적으로 제공하는 매커니즘 중 하나다.
여기서 더 나아간 심화 예시
둘 다 레퍼런스로 가져온다면
Member findRm1 = em.getReference(Member.class, member1.getId());
System.out.println("findRm1 = " + findRm1.getClass());
Member rfM = em.getReference(Member.class, member1.getId());
System.out.println("rfM = " + rfM.getClass());
System.out.println("a == a : " + (findRm1 == rfM));
당연히 둘 다 프록시로 반환 그리고 true도 나와야 함.
여기서 흥미로운 게 있는데
이거를
Member refMember = em.getReference(Member.class, member1.getId()); // Proxy
System.out.println("refMember = " + refMember.getClass());
Member findMember = em.getReference(Member.class, member1.getId()); // Member
System.out.println("findMember = " + findMember.getClass());
System.out.println("a == a : " + (refMember == findMember));
이렇게 수정한다면
신기하게도 둘 다 프록시가 반환이 된다!!!?!?!!!!!
그리고 true도 나오고.
이유
proxy가 먼 한 번 조회가 되면, em.find() 해서 proxy를 반환을 해버린다.
그래야 == 비교를 true로 보장할 수가 있어서....!!!
핵심은 우리가 proxy든 아니든 개발에 문제가 없게 하는 것이 중요하다.
준영속 상태일 때 초기화하면 문제 발생 예시
일단 이정도로 코드를 세팅하고
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 member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
em.flush();
em.clear();
Member referMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + referMember.getClass()); // Proxy
referMember.getUsername(); // 여기서 호출
// 이 때 DB에 쿼리가 날아가면서 프록시 객체가 호출
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
콘솔 로그 보면
refMember = class inflearn.exjpa.jpaExample.Member$HibernateProxy$s7WhieCJ
// 여기서 확인했고
01:03:01.642 [main] DEBUG org.hibernate.internal.SessionImpl - Initializing proxy: [inflearn.exjpa.jpaExample.Member#1]
01:03:01.646 [main] DEBUG org.hibernate.SQL -
// 쿼리 날아감
Hibernate:
select
member0_.MEMBER_ID as member_i1_3_0_,
member0_.createdBy as createdb2_3_0_,
member0_.createdDate as createdd3_3_0_,
member0_.lastModifiedBy as lastmodi4_3_0_,
member0_.lastModifiedDate as lastmodi5_3_0_,
member0_.TEAM_ID as team_id7_3_0_,
member0_.USERNAME as username6_3_0_,
team1_.TEAM_ID as team_id1_7_1_,
team1_.createdBy as createdb2_7_1_,
team1_.createdDate as createdd3_7_1_,
team1_.lastModifiedBy as lastmodi4_7_1_,
team1_.lastModifiedDate as lastmodi5_7_1_,
team1_.name as name6_7_1_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.TEAM_ID
where
member0_.MEMBER_ID=?
그런데...
detach 해버리면
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 member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
em.flush();
em.clear();
Member referMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + referMember.getClass()); // Proxy
em.detach(referMember); // 여기서 영속성 컨텍스트와 연결을 해지함
referMember.getUsername()); // 유저네임 호출
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace(); // 하나 찍어봄
} finally {
em.close();
}
emf.close();
}
}
결과 확인하면
01:10:58.916 [main] DEBUG org.hibernate.SQL -
Hibernate:
/* insert inflearn.exjpa.jpaExample.Member
*/ insert
into
Member
(createdBy, createdDate, lastModifiedBy, lastModifiedDate, USERNAME, MEMBER_ID)
values
(?, ?, ?, ?, ?, ?)
refMember = class inflearn.exjpa.jpaExample.Member$HibernateProxy$XLTnDmr5
01:10:58.935 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - rolling back
01:10:58.936 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
01:10:58.936 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
01:10:58.937 [main] DEBUG org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl - HHH000420: Closing un-released batch
01:10:58.937 [main] DEBUG org.hibernate.internal.SessionFactoryImpl - HHH000031: Closing
01:10:58.938 [main] DEBUG org.hibernate.type.spi.TypeConfiguration$Scope - Un-scoping TypeConfiguration [org.hibernate.type.spi.TypeConfiguration$Scope@53ed80d3] from SessionFactory [org.hibernate.internal.SessionFactoryImpl@72ba28ee]
01:10:58.938 [main] DEBUG org.hibernate.service.internal.AbstractServiceRegistryImpl - Implicitly destroying ServiceRegistry on de-registration of all child ServiceRegistries
01:10:58.938 [main] INFO org.hibernate.orm.connections.pooling - HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]
01:10:58.940 [main] DEBUG org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl - Implicitly destroying Boot-strap registry on de-registration of all child ServiceRegistries
org.hibernate.LazyInitializationException: could not initialize proxy [inflearn.exjpa.jpaExample.Member#1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at inflearn.exjpa.jpaExample.Member$HibernateProxy$XLTnDmr5.getUsername(Unknown Source)
at inflearn.exjpa.jpaExample.JpaMain.main(JpaMain.java:28)
종료 코드 0(으)로 완료된 프로세스
org.hibernate.LazyInitializationException: could not initialize proxy [inflearn.exjpa.jpaExample.Member#1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at inflearn.exjpa.jpaExample.Member$HibernateProxy$XLTnDmr5.getUsername(Unknown Source)
at inflearn.exjpa.jpaExample.JpaMain.main(JpaMain.java:28)
이렇게 에러가 나온다!!
JPA 입장에서는 영속성 컨텍스트의 도움을 받아서 데이터를 불러와서 초기화 해야 하는데,
그 전에 detach를 해버리면 영속성 컨텍스트의 관리 범주를 벗어난 것이 됨.
Member referMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + referMember.getClass()); // Proxy
em.close() ; // 아예 닫아버림
또는 em.clear(); // 아예 날려버림
referMember.getUsername()); // 유저네임 호출
tx.commit();
// => 이 경우도 동일하게 에러가 난다.!!!!
프록시 확인
1) 예시
먼저 초기화 안 한 상태
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 member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
em.flush();
em.clear();
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember" + refMember.getClass()); // Proxy
// 프록시 인스턴스의 초기화 여부 확인
// 현재 초기화하지 않은 상태이다. => false
System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace(); // 하나 찍어봄
} finally {
em.close();
}
emf.close();
}
}
false 출력됨
그럼 초기화 코드 삽입해보면
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember" + refMember.getClass()); // Proxy
refMember.getUsername(); //초기화 수행
// 프록시 인스턴스의 초기화 여부 확인
// 위에서 초기화 수행
System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
true 출력됨
2) 예시
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember 클래스 이름 = " + refMember.getClass().getName()); // Proxy
refMember.getUsername(); //초기화 수행
3) 강제초기화 예시
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember" + refMember.getClass()); // Proxy
// 강제 초기화
Hibernate.initialize(refMember);
쿼리 나가는 거 확인 가능
refMember = class inflearn.exjpa.jpaExample.Member$HibernateProxy$vqNrVYI9
01:43:35.654 [main] DEBUG org.hibernate.internal.SessionImpl - Initializing proxy: [inflearn.exjpa.jpaExample.Member#1]
01:43:35.658 [main] DEBUG org.hibernate.SQL -
Hibernate:
select
member0_.MEMBER_ID as member_i1_3_0_,
member0_.createdBy as createdb2_3_0_,
member0_.createdDate as createdd3_3_0_,
member0_.lastModifiedBy as lastmodi4_3_0_,
member0_.lastModifiedDate as lastmodi5_3_0_,
member0_.TEAM_ID as team_id7_3_0_,
member0_.USERNAME as username6_3_0_,
team1_.TEAM_ID as team_id1_7_1_,
team1_.createdBy as createdb2_7_1_,
team1_.createdDate as createdd3_7_1_,
team1_.lastModifiedBy as lastmodi4_7_1_,
team1_.lastModifiedDate as lastmodi5_7_1_,
team1_.name as name6_7_1_
from
Member member0_
left outer join
Team team1_
on member0_.TEAM_ID=team1_.TEAM_ID
where
member0_.MEMBER_ID=?
실무에서는 getReference() 거의 쓰지 않으나, 이 매커니즘을 이해해야 즉시 로딩과 지연 로딩을 이해할 수 있기 때문에
과하게 자세히 설명.