<aside> ❗
[두 가지 경우]
<aside> ❗
[em.find()
VS em.getReference()
]
em.find()
: 데이터베이스를 통해서 실제 엔티티 객체 조회
em.getReference()
: 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
→ DB에 쿼리가 안 나갔는데 객체가 조회가 되는 것프록시 클래스는 구조는 동일하지만 값이 모두 비어있다고 보면 됨
target은 진짜 참조를 가리킨다.
// 처음에는 null로 비어있음프록시 클래스는 실제 엔티티를 상속 받아서 만들어진다.
// 하이버네이트가 내부적으로 만들어 냄Member member = new Member();
member.setName("hello");
em.persist(member);
em.flush();
em.clear();
//프록시 객체를 가져옴
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember = " + findMember.getClass());
// 값이 실제 사용되는 시점에 쿼리가 나간다.
// 찾을 때, ID값을 알고 있으니 조회하지 않음
System.out.println("findMember.getId() = " + findMember.getId());
// Name의 값은 알 수 없으므로 영속성 컨텍스트에 실제 객체를 요청
System.out.println("findMember.getName() = " + findMember.getName());
System.out.println("findMember.getName() = " + findMember.getName());
System.out.println("findMember = " + findMember.getClass());
/*
// 하이버네이트가 강제로 만든 프록시 클래스
findMember = class jpabook.study.domain.Member$HibernateProxy$3UMPnBJl
*/
</aside>
<aside> ❗
진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨
(이론상)프록시 객체는 **실제 객체의 참조(target)**를 보관
프록시 객체를 호출
하면 프록시 객체는 실제 객체의 메서드 호출
// member에 프록시 객체를 저장함
Member member = em.getReference(Member.class, "id1");
member.getName();
JPA가 영속성 컨텍스트에 요청
( 진짜 Member 객체를 가져오도록 )영속성 컨텍스트는 DB를 조회
해서 진짜 Member 엔티티를 생성target이 진짜 엔티티를 참조하도록 함
target.getName()
수행초기화
- DB를 통해서 데이터를 가져와 엔티티를 만드는 과정
</aside><aside> ❗
한 번만 초기화
초기화 할 때
, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님
초기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능한 것
즉, 프록시 클래스의 target에 참조값이 들어가는 것
따라서 타입 체크시 주의해야함
** **(== 비교 실패, 대신 instance of 사용해야 함)
****Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member2.getId());
// 절대 타입 비교를 == 을 사용해서는 안 된다.
System.out.println(m1.getClass() == m2.getClass()); // false
영속성 컨텍스트에 찾는 엔티티가 이미 있으면
em.getReference()
를 호출해도
실제 엔티티 반환
즉, 영속성 컨테이너에서 해당 엔티티를 관리하는 시점에 em.getReference()를 호출해도 실제 엔티티를 반환함
→ 이미 영속성 컨텍스트에 있기 때문
→ 영속성 컨텍스트에 프록시 클래스가 등록
되면 이후 find()로 가져와도
1차 캐시에 있는 프록시 클래스를 반환
한다. (반대도 마찬가지)
같은 영속성 컨텍스트에서 같은 엔티티 ID를 가지는 객체는 항상 같은 인스턴스를 반환하도록 보장
함Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member2.getId());
// 영속성 캐시에서 가져오기 때문에 프록시가 m2에 저장됨
m2 = em.find(Member.class, member2.getId());
System.out.println(m2.getClass().getName());
System.out.println(m1.getClass() == m2.getClass());
/*
jpabook.study.domain.Member$HibernateProxy$SWAvcM1M
false
*/
준영속 상태일 때, 프록시를 초기화하면 문제 발생
(하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)Member member = em.getReference(Member.class, member.getId());
// member는 비영속 상태가 됨 -> 영속성 컨텍스트 도움을 받지 못함
em.detach(member);
member.getName()
/*
org.hibernate.LazyInitializationException 예외를 터트림
could not initialize proxy 오류 발생
no Session
*/
</aside>
<aside> ❗
PersistenceUnitUtil.isLoaded(Object entity)
emf.getPersistenceUnitUtil().isLoaded(member);
// false -> 초기화 X
// true -> 초기화 O
Hibernate.initialize(entity)
[참고]
<aside> ❗
지연 로딩 Lazy을 사용
해서 프록시로 조회@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team; // team을 프록시 타입으로 조회함, 즉 Member만 DB에서 조회
}
</aside>
<aside> ❗
초기화를 통해 실제 엔티티를 가져와 프록시가 참조하도록 한다
.Team team = member.getTeam();
team.getName(); // 실제 team을 사용하는 시점에 초기화(DB 조회)
</aside>
<aside> ❗
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
</aside>
<aside> ❗
조인을 사용해서 SQL 한번에 함께 조회
<aside> ❗
</aside>