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");
프록시 객체는 실제 객체에 대한 참조이므로 프록시의 메소드를 호출하면 실제 객체의 메소드가 호출 됩니다.
org.hibernate.LazyInitializationException
이 발생합니다.
// 준영속 상태가 된 프록시 객체에 초기화를 요청하면 에러가납니다.
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를 조회해서 실제 엔티티 객체를 생성합니다.
source
// 식별자가 보관되고 초기화 되지 않음
Team teamA = em.getReference(Team.class, "A");
System.out.println(teamA.getId());
-----
Aug 15, 2023 4:30:08 PM org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [name: myApp]
Aug 15, 2023 4:30:08 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 5.4.32.Final
Aug 15, 2023 4:30:08 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
Aug 15, 2023 4:30:08 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Aug 15, 2023 4:30:08 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [org.postgresql.Driver] at URL [jdbc:postgresql://localhost:5432/postgres?currentSchema=jpa]
Aug 15, 2023 4:30:08 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {password=****, user=postgres}
Aug 15, 2023 4:30:08 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Aug 15, 2023 4:30:08 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Aug 15, 2023 4:30:08 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.PostgreSQL10Dialect
Aug 15, 2023 4:30:08 PM org.hibernate.resource.transaction.backend.jdbc.internal.DdlTransactionIsolatorNonJtaImpl getIsolatedConnection
INFO: HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@2b289ac9] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
Aug 15, 2023 4:30:09 PM org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
A
Aug 15, 2023 4:30:09 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PoolState stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:postgresql://localhost:5432/postgres?currentSchema=jpa]
Property
는 초기화를 진행하지 않지만, Field
는 초기화를 진행합니다.
@Access(AccessType.FIELD)일 땐 getId() 메소드가 id만 조회하는 메소드인지 다른 필드까지 활용해서 어떤 일을 하는 메소드인지 알지 못해서 프록시 객체를 초기화 한다.
라고 책에 적혀 있는데 전혀 이해가 안됩니다. 반대라고 생각했어요 (Propery) 일 때 초기화가 되어야 한다고 생각했는데.. 그러고 아래 소스코드를 실행 해보면 둘 다 DB 조회는 일어나지 않는 것 같아서 더 혼란스럽습니다. 😵source
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myApp");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
Team teamA = em.getReference(Team.class, "A");
System.out.println("id : " + teamA.getId());
System.out.println("class : " + teamA.getClass());
emf.close();
}
-----
id : A
class : class org.example.ch8.proxy.accessType.field.Team$HibernateProxy$mFw53v4i
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myApp");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
Team teamA = em.getReference(Team.class, "A");
System.out.println("id : " + teamA.getId());
System.out.println("class : " + teamA.getClass());
emf.close();
}
-----
id : A
class : class org.example.ch8.proxy.accessType.property.Team$HibernateProxy$XSeNbh7u
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