나를 기록하다
article thumbnail
Published 2023. 6. 24. 15:23
[TIL-8 / 230623] JPA 프록시 TIL
반응형

1. 1. 프록시

1.1. Member를 조회할 때 Team도 함께 조회해야 할까?

member, team

  • 회원과 팀 함께 출력
<java />
public void printUserAndTeam(String memberId) { Member member = em.find(Member.class, memberId); Team team = member.getTeam(); System.out.println("회원 이름: " + member.getUsername()); System.out.println("소속팀: " + team.getName()); }
  • 회원만 출력
<java />
public void printUser(String memberId) { Member member = em.find(Member.class, memberId); Team team = member.getTeam(); System.out.println("회원 이름: " + member.getUsername()); }

회원과 팀을 함께 출력하기 위해서는 Member와 Team을 모두 조회해야 하지만, 회원만 출력할 때도 Team을 조회해야 할까?

1.2. 프록시 기초

  • em.find() vs em.getReference()
  • em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
  • em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

em.getReference()

1.3. em.find() 예시

  • 코드
<java />
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(); }
  • 결과
<java />
Hibernate: select member0_.MEMBER_ID as MEMBER_I1_3_0_, member0_.INSERT_MEMBER as INSERT_M2_3_0_, member0_.createdDate as createdD3_3_0_, member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_, team1_.createdDate as createdD3_7_1_, team1_.UPDATE_MEMBER as UPDATE_M4_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=? findMember.id = 1 findMember.username = hello

1.4. em.getReference() 예시 1

  • 코드
<java />
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()); // System.out.println("findMember.id = " + findMember.getId()); // System.out.println("findMember.username = " + findMember.getUsername()); tx.commit(); }
  • 결과
<java />
Hibernate: call next value for hibernate_sequence Hibernate: /* insert hellojpa.Member */ insert into Member (INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, USERNAME, MEMBER_ID) values (?, ?, ?, ?, ?, ?)

1.5. em.getReference() 예시 2

  • 코드
<java />
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()); System.out.println("findMember.id = " + findMember.getId()); System.out.println("findMember.username = " + findMember.getUsername()); tx.commit(); }
  • 결과
<java />
Hibernate: select member0_.MEMBER_ID as MEMBER_I1_3_0_, member0_.INSERT_MEMBER as INSERT_M2_3_0_, member0_.createdDate as createdD3_3_0_, member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_, team1_.createdDate as createdD3_7_1_, team1_.UPDATE_MEMBER as UPDATE_M4_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=? findMember.username = hello

2. 프록시 특징

  1. 실제 클래스를 상속받아서 만들어진다.
  2. 실제 클래스와 겉 모양이 같다.
  3. 사용하는 입장에서는 진짜 객체인지, 프록시 객체인지 구분하지 않고 사용하면 된다.(이론상)
  4. 프록시 객체는 실제 객체의 참조(target)를 보관한다.
  5. 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.

프록시의 상속, 위임

 

2.1. 프록시 객체의 초기화

<code />
Member member = em.getReference(Member.class, “id1”); member.getName();

프록시 객체의 초기화 과정

 

3. 프록시의 특징 정리

3.1. 1. 프록시 객체는 처음 사용할 때 한 번만 초기화

3.2. 2. 프록시 객체를 초기화할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능

  • 2번 추가 설명 - 프록시는 교체되는 것이 아니고 내부의 타겟에 값이 채워지는 것
<java />
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()); System.out.println("before findMember = " + findMember.getClass()); System.out.println("findMember.username = " + findMember.getUsername()); System.out.println("after findMember = " + findMember.getClass()); tx.commit(); }
<java />
Hibernate: call next value for hibernate_sequence Hibernate: /* insert hellojpa.Member */ insert into Member (INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, USERNAME, MEMBER_ID) values (?, ?, ?, ?, ?, ?) before findMember = class hellojpa.Member$HibernateProxy$0q4UoZqm Hibernate: select member0_.MEMBER_ID as MEMBER_I1_3_0_, member0_.INSERT_MEMBER as INSERT_M2_3_0_, member0_.createdDate as createdD3_3_0_, member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_, team1_.createdDate as createdD3_7_1_, team1_.UPDATE_MEMBER as UPDATE_M4_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=? findMember.username = hello after findMember = class hellojpa.Member$HibernateProxy$0q4UoZqm

3.3. 3. 프록시 객체는 원본 엔티티를 상속받는다. 따라서 타입 체크시 주의해야한다.(== 비교 실패, 대신 instance of 사용)

  • 3번 추가 설명 - em.find()와 em.find()
<java />
Member m1 = em.find(Member.class, member1.getId()); Member m2 = em.find(Member.class, member2.getId()); System.out.println("m1 == m2 -> " + (m1.getClass() == m2.getClass()));
<java />
m1 == m2 -> true
  • 3번 추가 설명 - em.find()와 em.getReference()
<java />
Member m1 = em.find(Member.class, member1.getId()); Member m2 = em.getReference(Member.class, member2.getId()); System.out.println("m1 == m2 -> " + (m1.getClass() == m2.getClass()));
<java />
m1 == m2 -> False

→ 실제로 업무를 할 때는 객체가 실제로 넘어오는지, 프록시로 넘어오는지 알 수 없다. 따라서 절대로 == 비교는 해서는 안된다. instance of를 사용하라.

<code />
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 m1 = em.find(Member.class, member1.getId()); Member m2 = em.getReference(Member.class, member2.getId()); logic(m1, m2); tx.commit(); } ... private static void logic(Member m1, Member m2) { System.out.println("m1 == m2 -> " + (m1 instanceof Member)); System.out.println("m1 == m2 -> " + (m2 instanceof Member)); }
<code />
m1 == m2 -> true m1 == m2 -> true

3.4. 4. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다.

  • 이유 1 - 영속성 컨텍스트에 있는데 굳이 프록시로 가져와봐야 아무 이점이 없다. 원본을 반환하는 것이 성능 최적화에서도 더 이점이 있다.
  • 이유 2 - JPA에서는 자바 컬렉션에서 가져온 것을 == 비교하듯이 ==비교가 한 영속성 컨텍스트에서 가져오고 pk가 같으면 true를 반환한다.
  • 4번 추가 설명 1
<code />
try { Member member1 = new Member(); member1.setUsername("member1"); em.persist(member1); em.flush(); em.clear(); Member m1 = em.find(Member.class, member1.getId()); System.out.println("m1 = " + m1.getClass()); Member reference = em.getReference(Member.class, member1.getId()); System.out.println("reference = " + reference.getClass()); tx.commit(); } System.out.println("a == a -> " + (m1 == reference));
<code />
m1 = class hellojpa.Member reference = class hellojpa.Member a == a -> true
  • 4번 추가 설명 2 - m1과 reference 모두 getReference를 사용해도 같은 프록시로 반환 → m1 == reference가 성립해야 하기 때문
<code />
try { Member member1 = new Member(); member1.setUsername("member1"); em.persist(member1); em.flush(); em.clear(); Member m1 = em.getReference(Member.class, member1.getId()); System.out.println("m1 = " + m1.getClass()); Member reference = em.getReference(Member.class, member1.getId()); System.out.println("reference = " + reference.getClass()); System.out.println("a == a -> " + (m1 == reference)); tx.commit(); }
<code />
m1 = class hellojpa.Member$HibernateProxy$tv7w3yGh reference = class hellojpa.Member$HibernateProxy$tv7w3yGh a == a -> true
  • 4번 추가 설명 3 - 프록시로 한번 조회가 되면 em.find()에서도 프록시로 반환을 한다. == 비교를 맞추기 위해서.
  • 핵심은 프록시든 아니든 개발에 문제가 없게 하는 것이 중요함. 어떻게든 JPA에서는 refMember == findMember 비교를 true로 맞추려 한다.
<code />
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()); Member findMember = em.find(Member.class, member1.getId()); System.out.println("findMember = " + findMember.getClass()); System.out.println("a == a -> " + (refMember == findMember)); tx.commit(); }
<code />
refMember = class hellojpa.Member$HibernateProxy$LMOvPJAY Hibernate: select member0_.MEMBER_ID as MEMBER_I1_3_0_, member0_.INSERT_MEMBER as INSERT_M2_3_0_, member0_.createdDate as createdD3_3_0_, member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_, team1_.createdDate as createdD3_7_1_, team1_.UPDATE_MEMBER as UPDATE_M4_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=? findMember = class hellojpa.Member$HibernateProxy$LMOvPJAY a == a -> true

3.5. 5. 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생한다.

(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터뜨린다.)

  • 5번 추가 설명 - detach(), close(), clear()를 사용할 시 영속성 컨텍스트가 날라감 → refMember는 영속성 컨텍스트의 도움을 받지 못함 → LazyInitializationException: could not initialize proxy 오류
<code />
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 em.detach(refMember); // 준영속 // em.close(); System.out.println("refMember = " + refMember.getUsername()); tx.commit(); }
<code />
Hibernate: call next value for hibernate_sequence Hibernate: /* insert hellojpa.Member */ insert into Member (INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, USERNAME, MEMBER_ID) values (?, ?, ?, ?, ?, ?) refMember = class hellojpa.Member$HibernateProxy$1XjTqsMB org.hibernate.LazyInitializationException: could not initialize proxy [hellojpa.Member#1] - no Session at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:169) at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309) at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45) at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) at hellojpa.Member$HibernateProxy$1XjTqsMB.getUsername(Unknown Source) at hellojpa.JpaMain.main(JpaMain.java:30) Jun 23, 2023 9:46:22 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop INFO: HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]

4. 프록시 확인

4.1. 1. 프록시 인스턴스의 초기화 여부 확인 - emf.getPersistenceUnitUtil().isLoaded(refMember));

  • 초기화 전 isLoaded = false
<code />
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 System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember)); tx.commit(); }
<code />
Hibernate: call next value for hibernate_sequence Hibernate: /* insert hellojpa.Member */ insert into Member (INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, USERNAME, MEMBER_ID) values (?, ?, ?, ?, ?, ?) refMember = class hellojpa.Member$HibernateProxy$HQDEJLw0 isLoaded = false
  • 초기화 후 isLoaded = true
<code />
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 refMember.getUsername(); System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember)); tx.commit(); }
<code />
Hibernate: call next value for hibernate_sequence Hibernate: /* insert hellojpa.Member */ insert into Member (INSERT_MEMBER, createdDate, UPDATE_MEMBER, lastModifiedDate, USERNAME, MEMBER_ID) values (?, ?, ?, ?, ?, ?) refMember = class hellojpa.Member$HibernateProxy$KSgjPxUU Hibernate: select member0_.MEMBER_ID as MEMBER_I1_3_0_, member0_.INSERT_MEMBER as INSERT_M2_3_0_, member0_.createdDate as createdD3_3_0_, member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_, team1_.createdDate as createdD3_7_1_, team1_.UPDATE_MEMBER as UPDATE_M4_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=? isLoaded = true

4.2. 2. 프록시 클래스 확인 방법 - refMember.getClass().getName() 출력

<code />
System.out.println("refMember = " + refMember.getClass().getName()); // Proxy
<code />
System.out.println("refMember = " + refMember.getClass().getName()); // Proxy

4.3. 3. 프록시 강제 초기화 - JPA 표준은 강제 초기화가 없다. Hibernate.initialize(refMember); 이렇게 사용

<code />
Member refMember = em.getReference(Member.class, member1.getId()); System.out.println("refMember = " + refMember.getClass().getName()); // Proxy Hibernate.initialize(refMember); // 강제 초기화
<code />
Hibernate: select member0_.MEMBER_ID as MEMBER_I1_3_0_, member0_.INSERT_MEMBER as INSERT_M2_3_0_, member0_.createdDate as createdD3_3_0_, member0_.UPDATE_MEMBER as UPDATE_M4_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_.INSERT_MEMBER as INSERT_M2_7_1_, team1_.createdDate as createdD3_7_1_, team1_.UPDATE_MEMBER as UPDATE_M4_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=?
반응형
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!