ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • #JPA-13 프록시
    JPA 2021. 2. 19. 17:32

    [출처] 인프런 김영한 강사님 -자바 ORM 표준 JPA 프로그래밍 기본

    프록시 학습전 의문? Member를 조회할 때 Team도 함께 조회해야 할까?

     

    회원과 팀 함께 출력

     

    public void printUserAndTeam(String memberId) {
      Member member = em.find(Member.class, memberId);
      Team team = member.getTeam();
      System.out.println("회원 이름: " + member.getUsername());
      System.out.println("소속팀: " + team.getName()); 
    }

    회원만 출력

    public void printUser(String memberId) {
      Member member = em.find(Member.class, memberId);
      Team team = member.getTeam();
      System.out.println("회원 이름: " + member.getUsername()); 
    }
    

     

    프록시 기초 

    • em.find() vs em.getReference() 

    • em.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회 

    Member member = new Member();
    member.setUsername("hello");
    
    em.persist(member);
    
    em.flush();
    em.clear();
    
    Member findMember = em.find(Member.class, member.getId());
    System.out.println("findMember.getId() = " + findMember.getId());
    System.out.println("findMember.getUsername() = " + findMember.getUsername());
    
    tx.commit();

    • em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

    (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.getId() = " + findMember.getId());
                System.out.println("findMember.getUsername() = " + findMember.getUsername());

    • em.getReference할때까지는 쿼리를 호출안하고 이 값을 사용하는 시점에 쿼리가 날라간다.

    • findMember.getId() 같은 경우는 위에서 member.getId()로 collection에 있는 값을 넣었기때문에 DB에 안다녀와도 알수 있지만.

    • findMember.getUsername같은 경우에는 DB에 있는거 이고 findMember가 가짜이기때문에 findMember.getUsername을 호출 하는 시점에 JPA가 DB에 쿼리를 날리고 findMember에 값을 채운다음에 결과를 보여준다.

    Proxy~  hibernate가 만들어낸 가짜라고 볼 수 있다.

    hibernate가 가짜 entity를준다. 껍데기는 똑같은데 내부가 텅텅빈것이다 .

    안에잇는 target이 진짜 reference를 가르킨다. target은 초기에는 없고 초기에는 껍데기에 아이디 값만 가지고 있는 애를 반환한다.

     

    프록시 특징

    • 실제 클래스를 상속 받아서 만들어짐

    • 실제 클래스와 겉 모양이 같다.

    • 사용하는 입장에서는 진짜 객체인지
 프록시 객체인지 구분하지 않고 


    사용하면 됨(이론상)

    -> 상속관계는 상속해도 부모타입 보고 쓰면 되기 때문에.

     

    • 프록시 객체는 실제 객체의 참조(target)를 보관 

    • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드 호출

    getName을호출하면 실제 타겟에있는 getName을 대신 조회해준다. 하지만 처음엔 target이 없음.

     

    프록시 객체의 초기화

    Member member = em.getReference(Member.class, "id1"); //프록시 객체를 가져옴

    member.getName(); //진짜 객체를 가져옴

    1. getName을 했을때 taget에 진짜 entity가 없으면

    2. JPA가 영속성 컨텍스트에 요청을 한다.

    3~4. 영속성 컨테르가 실제 entity객체를 가져오고

    5. 프록시의 target에 진짜 Entity를 연결해준다.

    프록시의 특징

    • 프록시 객체는 처음 사용할 때 한 번만 초기화 

    • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님, 초 기화되면 프록시 객체를 통해서 실제 엔티티에 접근 가능 

    ->프록시는 교체되는 것이 아니라 내부에 타겟에만 갑싱 채워지는 것.

    • 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야함 (== 비 교 실패, 대신 instance of 사용) 

                Member member1 = new Member();
                member1.setUsername("hello1");
                em.persist(member1);
    
                Member member2 = new Member();
                member2.setUsername("hello2");
                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())); //type 비교
    
                tx.commit();

    type이 같기때문에

                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())); //type 비교

    type비교이기때문에 상속관계 이런것은 안된다.

    ->실제 비지니스 로직에서는 type을 비교할때 이게 proxy로 들어올지 실제로 들어올지 모르기때문에 type비교는 ==으로 하면 안되고 instanceof를 사용해야한다.

     private static void logic(Member m1, Member m2){
            System.out.println("m1 = Member : " + (m1 instanceof Member));
            System.out.println("m2 = Member : " + (m2 instanceof Member));
    
        }

    instanceof로 비교

    • 속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해 도 실제 엔티티 반환 

                Member member1 = new Member();
                member1.setUsername("hello1");
                em.persist(member1);
    
                em.flush();
                em.clear();
    
                Member m1 = em.find(Member.class, member1.getId());
                System.out.println("m1.getClass() = " + m1.getClass());
    
                Member reference = em.getReference(Member.class, m1.getId());
                System.out.println("reference.getClass() = " + reference.getClass());
    
                tx.commit();

    reference가 proxy가아니라 실제 entity인 이유

       1. 이미 Member를 영속성 컨텍스트 1차캐시에 올려놧는데 proxy를 가져와서 얻을 이점이없다.

           원본을 바로 반환하는게 성능 최적화에 적합하다. 

       2. JPA에서는 m1 == reference는 항상 true가나와야한다. m1이 실제든 proxy든 상관없이

          JPA에서는 마치 자바 컬렌션에서 가져온값을 == 비교 하듯이 ==비교가 한 영속성 컨텍스트 안에서 가져온놈이고        PK가 똑같으면 JPA는 항상 TRUE를 반환해야한다. (JPA는 한 트렌젝션 안에서 같음을 보장해준다.) 

     

                 Member reference = em.getReference(Member.class, member1.getId());
                System.out.println("reference.getClass() = " + reference.getClass()); //proxy
    
                Member m1 = em.find(Member.class, member1.getId());
                System.out.println("m1.getClass() = " + m1.getClass()); //Member
    
                System.out.println("reference = m1 " + (reference == m1));

    프록시가 한번 조회되면 그다음  FIND도 PROXY로 맞춰버린다 그래야 == 비교를 할 수 있기 때문이다.

     

    속성 컨텍스트의 도움을 받을 수 없는 준속 상태일 때, 프록시를 초기화하면 문제 발생
 (하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림)

                Member member1 = new Member();
                member1.setUsername("hello1");
                em.persist(member1);
    
                em.flush();
                em.clear();
    
                //1. proxy
                Member reference = em.getReference(Member.class, member1.getId());
                //2. query가 날라가면서 proxy 초기화
                System.out.println("reference.getClass() = " + reference.getClass()); //proxy
    
                //3. 그런데? 프록시의 초기화 요청이 영속성 컨텍스트를 통해서 하는데, 영속성 컨텍스트를 꺼버리면? 또는 em.detach로 관리안하게 해버리면?
    
                em.close();
                //or
                em.detach(reference);
                tx.commit();

    clear도 똑같다. clear는 끈건아니지만 다지우고 새로만드는 것이기 떄문에 이런것을 마나는 순간 reference는 영속성 컨텍스트의 도움을 못받는 것이다. could not initialize proxy에러를 만난다.

     

    프록시 확인 

    • 프록시 인스턴스의 초기화 여부 확인
 PersistenceUnitUtil.isLoaded(Object entity)

       - 프록시가 초기화 됐는지 안됐는지.

          emf.getPersistenceUnitUtil().isLoaded(reference));

    • 프록시 클래스 확인 방법
 entity.getClass().getName() 출력(..javasist.. or HibernateProxy…) 

    • 프록시 강제 초기화
 org.hibernate.Hibernate.initialize(entity);  or Hibernate.initialize(entity);

    • 참고: JPA 표준은 강제 초기화 없음
 강제 호출: member.getName()

     

    'JPA' 카테고리의 다른 글

    #JPA-15 속성 전이: CASCADE  (0) 2021.02.19
    #JPA-14 즉시 로딩과 지연 로딩  (0) 2021.02.19
    #JPA-12 상속관계 매핑  (0) 2021.02.19
    #JPA-11 다양한 연관관계 매핑  (0) 2021.02.18
    #JPA-10 연관관계 매핑 기초  (0) 2021.02.17

    댓글

Designed by Tistory.