printMember()member 만 사용하므로 team 까지 미리 조회해 두는 건 좋지 않습니다.

이 문제를 해결하기 위해 지연로딩을 제공합니다.

member.getTeam() 처럼 실제 값을 사용할 때 DB에서 내용을 조회 합니다.

지연로딩을 사용하기 위해선 실제 엔티티 객체 대신 DB 조회를 지연할 수 있는 가짜 엔티티 객체가 필요한데 이것을 프록시 객체라고 합니다.

프록시는 JPA 구현체가 구현해야하고 책은 하이버네이트 기준으로 설명합니다.

private static void printMember(EntityManager em) {
    Member member = em.find(Member.class, "cho");
    System.out.println("member's name = " + member.getUsername());
}

private static void printUserAndTeam(EntityManager em) {
    Member member = em.find(Member.class, "cho");
    System.out.println("member's name = " + member.getUsername());
    System.out.println("team's name = " + member.getTeam().getName());
}

프록시 기초

식별자로 엔티티 하나를 조회할 땐 em.find() 를 사용합니다. 이렇게 할 경우엔 DB를 조회합니다.

엔티티 사용하는 시점까지 DB 조회를 미루려면 em.getReference() 를 하면 됩니다.

em.getReference() 를 하면 JPA는 DB를 조회하지 않고 DB 접근을 위임한 프록시 객체를 반환합니다.

Member member = em.find(Member.class, "member1");
Member proxyMember = em.getReference(Member.class, "cho");

프록시 객체는 실제 객체에 대한 참조이므로 프록시의 메소드를 호출하면 실제 객체의 메소드가 호출 됩니다.

  1. 처음 사용할 때 한 번만 초기화 됩니다.
  2. 초기화를 해서 프록시 객체가 실제 엔티티가 되는 것은 아니고 프록시 객체를 통해 엔티티에 접근 할 수 있습니다.
  3. 초기화는 영속성 컨텍스트의 도움이 있어야 합니다. 그러므로 준영속 상태의 프록시를 초기화하면 org.hibernate.LazyInitializationException 이 발생합니다.
    • source
       // 준영속 상태가 된 프록시 객체에 초기화를 요청하면 에러가납니다.
       Member proxyMember = em.getReference(Member.class, "cho");
       em.close();
       proxyMember.getUsername();
       emf.close();
          
       -----
          
       Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy [org.example.ch8.proxy.Member#cho] - no Session
    

Java에서 구현할 수 있는 Proxy들 (Pure, JDK, CGLIB)

member.getName() 처럼 실제 사용될 때 DB를 조회해서 실제 엔티티 객체를 생성합니다.

Untitled

프록시와 식별자

Member member = em.find(Member.class, "cho");
Team proxyTeam = em.getReference(Team.class, "A");
member.setTeam(proxyTeam);

프록시 확인

Member proxyMember = em.getReference(Member.class, "cho");

System.out.println("isInitialized = " + emf.getPersistenceUnitUtil().isLoaded(proxyMember));
proxyMember.getUsername();
System.out.println("isInitialized = " + emf.getPersistenceUnitUtil().isLoaded(proxyMember));

-----

isInitialized = false
Hibernate: 
    select
        member0_.MEMBER_ID as member_i1_0_0_,
        member0_.team_TEAM_ID as team_tea3_0_0_,
        member0_.username as username2_0_0_,
        team1_.TEAM_ID as team_id1_1_1_,
        team1_.name as name2_1_1_ 
    from
        Member member0_ 
    left outer join
        Team team1_ 
            on member0_.team_TEAM_ID=team1_.TEAM_ID 
    where
        member0_.MEMBER_ID=?
isInitialized = true