JPA - 프록시

PPakSSam·2022년 1월 14일
1
post-thumbnail

아래의 포스트를 읽기전에 프록시 객체에 대한 개념이 부족하다면
About Proxy를 먼저 읽고 아래의 포스트를 읽는 것을 추천한다.

JPA에서 프록시란

  • 실제 클래스를 상속 받아서 만들어진다.
  • 실제 클래스와 겉모양이 같다.
  • 프록시 객체는 실제 객체의 참조(target)를 보관한다.
  • 프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.

참고

  • em.find() : 데이터베이스를 통해서 실제 엔티티 객체 반환
  • em.getReference() : 데이터베이스 조회를 미루는 프록시 객체 반환

프록시 객체의 초기화

Member member = em.getReference(Member.class, "id1"); // 프록시 객체 반환
member.getName(); // 프록시가 이 때 초기화된다.

주의점

프록시가 초기화되면 원본객체로 바뀌는 것이 아니라 프록시 객체의 타겟이 원본객체로 설정되는 것이다. 따라서 위의 코드에서 member.getName()을 하면 Member 원본객체의 메소드가 호출되는 것이 아니고, Member 프록시 객체의 메소드가 호출된 뒤 target에 의해 Member 원본 객체의 메소드가 호출되는 것이다.

프록시 특징

Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);

Member member2 = new Member();
member2.setUsername("member2");
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
  • 프록시 객체는 처음 사용할 때 한번만 초기화 된다.

  • 프록시 객체를 초기화할 때, 프록시 객체가 실제 엔티티로 바뀌는 것이 아니다.
    -> 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근하는 것이다.

  • 프록시 객체는 원본 엔티티를 상속받는다. 따라서 타입 체크시 주의해야한다.
    -> 원본 엔티티와 == 비교를 하면 실패한다. 따라서 instance of를 사용해야 한다.

Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member1.getId());
System.out.println(m1.getClass()); // class hellojpa.Member
System.out.println(m2.getClass()); // class hellojpa.Member

Member m2 = em.getReference(Member.class, member1.getId());
Member m1 = em.find(Member.class, member1.getId());
System.out.println(m1.getClass()); // class hellojpa.Member$HibernateProxy$l3UMcB5d
System.out.println(m2.getClass()); // class hellojpa.Member$HibernateProxy$l3UMcB5d
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다.
  • 반대로 em.getReference()를 먼저한 다음 em.find()를 하면 둘 다 프록시 객체를 반환한다.
    -> JPA는 한 트랜잭션 안에서 같은것을 조회하면 같은 객체임을 보장한다.
    -> 따라서 위의 코드와 같은 상황이 발생하는 것이다.

Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);

em.flush();
em.clear();

Member m2 = em.getReference(Member.class, member1.getId());

em.detach(m2); // 준영속 상태로 만들기
System.out.println(m2.getUsername()); // 예외 발생!
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
    -> 하이버네이트는 org.hibernate.LazyInitializationException 예외를 터뜨린다.

프록시 확인

emf.getPersistenceUnitUtil().isLoaded(Object obj)

프록시 인스턴스의 초기화 여부 확인

Member m2 = em.getReference(Member.class, member1.getId());
System.out.println(emf.getPersistenceUnitUtil().isLoaded(m2)); // false

m2.getUsername();
System.out.println(emf.getPersistenceUnitUtil().isLoaded(m2)); // true

entity.getClass().getName()

프록시 객체의 클래스를 확인할 수 있다.

Hibernate.initialize(Object obj)

프록시 객체를 강제로 초기화시킨다.

Member m2 = em.getReference(Member.class, member1.getId());
Hibernate.initialize(m2);
System.out.println(emf.getPersistenceUnitUtil().isLoaded(m2)); // true
profile
성장에 대한 경험을 공유하고픈 자발적 경험주의자

0개의 댓글