티스토리 뷰

[개발] - Spring/JPA 공부

프록시

완벽한 장면 2023. 8. 24. 02:44

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));

항상 true가 나온다.

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() 거의 쓰지 않으나, 이 매커니즘을 이해해야 즉시 로딩과 지연 로딩을 이해할 수 있기 때문에 

과하게 자세히 설명.

 

728x90
반응형

'[개발] - Spring > JPA 공부' 카테고리의 다른 글

즉시 로딩  (0) 2023.08.24
지연 로딩  (0) 2023.08.24
실전 예제 - 4. 상속관계 매핑  (0) 2023.08.23
@MappedSuperclass  (0) 2023.08.23
상속관계 매핑  (0) 2023.08.23
Comments
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
250x250