JAVA

[JAVA] 프록시

응디 2022. 1. 4. 17:19

em.find() VS em.getReference()

  • em.find() : DB를 통해서 실제 엔티티 객체 조회 (DB에 쿼리 나감)
  • em.getReference() : DB 조회를 미루는 가짜(프록시) 엔티티 객체 조회(DB에 쿼리가 안나가는데 객체가 조회됨)
Member member = new Member();
member.setUsername("hello");
            
em.persist(member);
em.flush();
em.clear(); // 영속성 컨텍스트 초기화

Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember = " + findMember.getClass());    // 하이버네이트가 내부 라이브러리를 사용해 강제로 만들어낸 가짜(프록시) 엔티티
System.out.println("findMember Id : " + findMember.getId());    // Id는 파라미터로 넣어줬기 때문에 DB를 안거치고 나옴
System.out.println("findMember Username : " + findMember.getUsername());    // 이때 쿼리가 바로 날아감

위와 같이 경우 Id는 findMember의 파라미터 값으로 넣어주어 굳이 DB에 접근하지 않았지만 Username은 받아온게 없기 때문에 DB로 바로 쿼리를 날려서 값을 채워넣어준다.

Proxy는 객체와 껍데기는 똑같은데 안에가 텅텅 비어있는것!

 


프록시 특징

  • 실제 클래스(엔티티)를 상속 받아서 만들어짐
  • 실제 클래스와 겉 모양이 같다.
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용해도 됨(이론상으로만)
  • 프록시 객체는 실제 객체의 참조(target)을 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

 

프록시 초기화

  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님
  • 초기화 되면 프록시 객체를 통해 실제 엔티티에 접근 가능
  • 프록시 객체는 원본 엔티티를 상속 받음, 따라서 타입 체크 시 주의!( == 대신에 instanceof 를 사용) → member1 instanceof Member
  • 영속성 컨텍스트에 이미 찾는 엔티티가 있으면 em.getReference()를 호출해도 실제 엔티티 반환
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일때, 프록시를 초기화 하면 장애 발생
    Member refMember = em.getReference(Member.class, member1.getId());
                System.out.println("refMember = " + refMember.getClass());
    
                em.detach(refMember);
                // em.clear();
                // em.close();
    
                System.out.println("refMmber result = " + refMember.getUsername());
    
  • em을 중간에 detach(영속성컨텍스트에서 꺼냄- 준영속 상태), clear(em을 초기화), close 시킬때 발생한다. (org.hibernate.LazyInitial... 발생 )
  • 이때 타입 비교를 위해 ==을 사용하면 원래는 getReference하면 엔티티가 아닌 프록시가 반환되지만 jpa 는 한 트랜잭션 안에서 프록시로 반환해봐야 이점이 없고(타입 비교 시 다를 수 있기 때문) 이미 영속성 컨텍스트에 객체가 존재하면 그냥 실제 엔티티를 반환한다.

※ 만약 프록시가 먼저 호출되고 그다음 find가 진행되면?

: JPA는 타입을 맞추는 의무?? 가 있기때문에 원래라면 em.find를 실행하면 Member를 반환하지만 아래의 경우 위에 타입이 먼저 프록시로 지정되었기 때문에 프록시로 맞춰 반환한다.

따라서 비교시 결과값 true!

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

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

Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember = " + refMember.getClass());  //Proxy가 반환 -> 앞에서 영속성 컨텍스트 비어있음

Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember = " + findMember.getClass()); // Member가 반환되겠지??

System.out.println("refMember == findMember : " + (refMember == findMember));

 

프록시 확인

  • 프록시 인스턴스의 초기화 여부 확인 : PersistenceUnitUtil.isLoaded(Object entity)
emf.getPersistenceUnitUtil().isLoaded(refMember);
  • 프록시 클래스 확인 방법 : entity.getClass().getName() 출력(..javasist.. or HibernateProxy)
  • 프록시 강제 초기화 : org.hibernate.Hibernate.initialize(entity)
  • 참고 : JPA 표준은 강제 초기화가 없음 위에 초기화는 하이버네이트에서 해주는것
  • 강제 호출 : member.getName()