이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.
em.find() vs em.getReference()
- em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
- em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class Example {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("example-unit");
EntityManager em = emf.createEntityManager();
// 엔티티의 참조를 가져오는 예시
em.getTransaction().begin();
// 예시로 사용할 엔티티 클래스와 기본 키
Long primaryKey = 1L;
MyEntity entity = em.getReference(MyEntity.class, primaryKey);
// entity를 실제로 사용하면 데이터베이스 조회가 발생
System.out.println(entity.getName());
em.getTransaction().commit();
em.close();
emf.close();
}
}
프록시 특징
- 실제 클래스를 상속 받아서 만들어짐
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 됨(이론상)
- 프록시 객체는 실제 객체의 참조(target)를 보관
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출
프록시 객체의 초기화
- 프록시 객체의 메서드를 호출하거나 필드에 접근
- 영속성 컨텍스트에 초기화 요청
- 영속성 컨텍스트는 DB를 조회하여 데이터를 가져옴
- 영속성 컨텍스트는 가져온 데이터를 바탕으로 실제 엔티티를 생성
- 프록시 객체가 실제 엔티티의 메서드를 호출하거나 필드에 접근
프록시 객체를 사용할 때의 주의사항
1.초기화 시점
- 프록시 객체는 처음 접근할 때 한 번만 초기화된다.
- 예를 들어, 프록시 객체의 메서드를 호출하거나 필드에 접근할 때 실제 데이터베이스 조회가 발생하여 초기화된다.
2.프록시 객체와 실제 엔티티의 차이
- 프록시 객체가 초기화되더라도, 이는 프록시 객체가 실제 엔티티로 변하는 것이 아니다.
- 초기화된 프록시 객체를 통해 실제 엔티티에 접근할 수 있다.
3.타입 체크 주의사항
- 프록시 객체는 원본 엔티티 클래스를 상속받는다. 따라서, == 연산자로 타입을 비교하면 실패할 수 있다.
- 대신, instanceof 연산자를 사용하여 프록시 객체인지 확인할 수 있다.
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);
Member member2 = new Member();
member1.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());
System.out.println("m1 = " + m1.getClass());
System.out.println("m2 = " + m2.getClass());
System.out.println("m1 == m2 = " + (m1 == m2));
4.영속성 컨텍스트의 역할
- 영속성 컨텍스트에 이미 찾고자 하는 엔티티가 존재하면, em.getReference()를 호출해도 실제 엔티티가 반환된다.
- 즉, 프록시 객체가 아닌 실제 엔티티 인스턴스가 반환된다.
Member member = new Member();
member.setUsername("member1");
em.persist(member);
em.flush();
em.clear();
Member m1 = em.find(Member.class, member.getID());
System.out.println("m1 = " + m1.getClass());
Member m2 = em.getReference(Member.class, member.getID());
System.out.println("m2 = " + m2.getClass());
System.out.println("m1 == m2 = " + (m1 == m2));
참고로 JPA에서 영속성 컨텍스트에서 하나의 트랜잭션안에 묶여있다면 진짜 엔티티든, 프록시든 객체 비교시 동일성을 보장한다.
Member member = new Member();
member.setUsername("member1");
em.persist(member);
em.flush();
em.clear();
Member m1 = em.getReference(Member.class, member.getID());
Member m2 = em.find(Member.class, member.getID());
System.out.println("m1 = " + m1.getClass());
System.out.println("m2 = " + m2.getClass());
System.out.println("m1 == m2 = " + (m1 == m2));
5.준영속 상태에서의 문제
- 준영속 상태(persistent context의 도움을 받을 수 없는 상태)에서 프록시 객체를 초기화하면 문제가 발생할 수 있다.
- 이는 LazyInitializationException 등의 예외를 초래할 수 있다.
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());
em.detach(m1);
m1.getUsername();
프록시 관련 메서드
PersistenceUnitUtil.isLoaded(Object entity)
프록시 인스턴스의 초기화 여부를 알려주는 메서드이다.
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("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(m1));
entity.getClass()
프록시 클래스를 확인하는 메서드이다.
Member m1 = em.getReference(Member.class, member1.getID());
System.out.println("m1 = " + m1.getClass());
Hibernate.initialize(entity)
프록시 객체를 강제로 초기화하는 메서드이다.
Hibernate.initialize(m1); //강제 초기화
참고: JPA 표준은 강제 초기화 없음
'Java Category > JPA' 카테고리의 다른 글
[JPA] 값 타입(Value Type) (0) | 2024.07.28 |
---|---|
[JPA] 즉시 로딩, 지연 로딩, 영속성 전이, 고아 객체 (0) | 2024.07.27 |
[JPA] 상속관계 매핑 (0) | 2024.07.25 |
[JPA] 다양한 연관관계 매핑 (5) | 2024.07.24 |
[JPA] 연관관계 매핑(단방향, 양방향)을 통한 객체 그래프 탐색 (0) | 2024.07.17 |