728x90
프록시란 무엇일까?
em.find()는 데이터베이스를 통해 실제 엔티티 객체를 조회한다면 em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체를 조회한다.
이게 무슨 말이냐면...
- 프록시는 실제 클래스를 상속 받아서 만들어지므로 겉모양이 같다.
- 프록시 객체는 실제 객체의 참조(target)를 보관한다.
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.(이론상의 얘기이다.)
예시를 들어보자.
Member member = em.getReference(Member.class, “id1”);
member.getName();
getReference()만 했을때는 실제로 DB에 접근하지 않는다.
getName()을 하면 비로소 '초기화' 요청이 일어나고 영속성 컨텍스트를 통해 DB 조회가 일어난다.
실제 엔티티가 이때 생성이 되고 target에 실제 엔티티를 연결하여 target의 getName()을 호출한다.
프록시의 특징(+예시 코드)
1. 프록시 객체는 처음 사용할 때 한번만 초기화 된다.
2. 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다. 초기화 되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능하다.
//멤버 세팅
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("before findMember = " + findMember.getClass());
//초기화
System.out.println("findMember.getName() = " + findMember.getName());
System.out.println("after findMember = " + findMember.getClass());
- before과 after값은 동일하다. 프록시는 한번만 초기화 되기 때문에 교체되지 않는다.
3. 프록시 객체는 원본 엔티티를 상속받는다. 따라서 타입 체크시 주의해야 한다.( == 비교 실패, 대신 instance of 사용)
Member member1 = new Member();
member1.setName("mem1");
em.persist(member1);
Member member2 = new Member();
member1.setName("mem2");
em.persist(member2);
em.flush();
em.clear();
Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.find(Member.class, member2.getId());
System.out.println("m1 == m2 " + (m1.getClass() == m2.getClass())); //true
Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member2.getId());
System.out.println("m1 == m2 " + (m1.getClass() == m2.getClass())); //false
System.out.println("m1 == m2 " + (m1 instanceof Member)); //true
System.out.println("m1 == m2 " + (m2 instanceof Member)); //true
4. 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다.
Member m1 = em.find(Member.class, member1.getId());
System.out.println("m1 = " + (m1.getClass()));//Member
Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference.getClass() = " + reference.getClass());//Member
System.out.println("a == a: "+ (m1 == reference)); //이 경우 JPA에서 무조건 true 보장
- 이미 영속성 컨텍스트에 Member가 있기때문에 Member를 가져옴(프록시로 가져와봐야 이점이 없다.)
- JPA에서는 이 예제의 m1 == reference는 항상 true여야 한다.(한 트랜잭션 안에서 같음을 보장해준다.)
5. 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제가 발생한다.(하이버네이트는
org.hibernate.LazyInitializationException 예외를 발생시킨다.)
Member reference = em.getReference(Member.class, member1.getId());
System.out.println("reference.getClass() = " + reference.getClass());//Proxy
em.close();
//혹은
em.detach(reference);
reference.getName();
//org.hibernate.LazyInitializationException 에러 발생
- 준영속 상태에서는 프록시가 초기화 되지 않았기 때문에 오류가 발생한다.
- JPA를 사용하다보면 많이 만나는 오류이므로 꼭 기억해두자!
프록시를 확인하는 메소드
- 프록시 인스턴스의 초기화 여부 확인: PersistenceUnitUtil.isLoaded(Object entity)
- 프록시 클래스 확인 방법: entity.getClass().getName() 출력(..javasist.. or HibernateProxy...)
- 프록시 강제 초기화: org.hibernate.Hibernate.initialize(entity);
- 참고: JPA 표준은 강제 초기화 없음 강제 호출: member.getName()
728x90
'💻dev > 🌱Java+Spring' 카테고리의 다른 글
JPA | 영속성 전이(CASCADE)와 고아 객체란? (0) | 2023.05.01 |
---|---|
JPA | 즉시 로딩(FetchType.EAGER)과 지연 로딩(FetchType.LAZY) (0) | 2023.05.01 |
JPA | 예제로 알아보는 상속관계 매핑 (예제-4) (0) | 2023.05.01 |
JPA | 상속관계 매핑이란? @MappedSuperClass란? (0) | 2023.04.30 |
JPA | 예제로 알아보는 연관관계 매핑 (예제-3) (0) | 2023.04.30 |