Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

프록시 본문

인프런/[인프런] 자바ORM 표준 JPA 프로그래밍

프록시

Seung__ 2021. 8. 12. 00:21

1. Member를 조회할 때 Team도 함께 조회해아 하나..??


 

 

2. 프록시 기초


  • JPA는 em.find()외에도 em.getReference()를 제공
  • em.find() vs em.getReference()
  • en.find() : 데이터베이스를 통해서 실제 엔티티 객체 조회
  • em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회.
    • DB에 쿼리가 안나가는데 객체가 조회가 됨!
  • Proxy는 진짜 객체와 껍데이는 똑같은데, 안이 텅 비었음.
  • 내부에 target이 있는데 진짜 레퍼런스를 가르킴.

  • em.getReference() 하는 시점에 select SQL 안나감.
    Member findMember = em.getReference(Member.class, member.getId());​
  • 그런데 em.getReference()로 가져온 값이 실제 사용되는 시점에는 쿼리가 날라간다.
    • get을 통해 값을 가져오는 시점.
    • 특히 userName은 DB에 값이 있기 때문에 DB에 쿼리를 날려서 값을 가져옴.
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember.getUserName() = " + findMember.getUserName());
System.out.println("findMember.getId() = " + findMember.getId());
Hibernate: 
    select
        member0_.MEMBER_ID as member_i1_4_0_,
        member0_.createdBy as createdb2_4_0_,
        member0_.createdDate as createdd3_4_0_,
        member0_.lastModifiedBy as lastmodi4_4_0_,
        member0_.lastModifiedDate as lastmodi5_4_0_,
        member0_.modifiedBy as modified6_4_0_,
        member0_.LOCKIER_ID as lockier_8_4_0_,
        member0_.TEAM_ID as team_id9_4_0_,
        member0_.USERNAME as username7_4_0_,
        locker1_.id as id1_3_1_,
        locker1_.name as name2_3_1_,
        team2_.TEAM_ID as team_id1_8_2_,
        team2_.createdBy as createdb2_8_2_,
        team2_.createdDate as createdd3_8_2_,
        team2_.lastModifiedBy as lastmodi4_8_2_,
        team2_.lastModifiedDate as lastmodi5_8_2_,
        team2_.modifiedBy as modified6_8_2_,
        team2_.name as name7_8_2_ 
    from
        Member member0_ 
    left outer join
        Locker locker1_ 
            on member0_.LOCKIER_ID=locker1_.id 
    left outer join
        Team team2_ 
            on member0_.TEAM_ID=team2_.TEAM_ID 
    where
        member0_.MEMBER_ID=?
findMember.getUserName() = hello
findMember.getId() = 1

 

  • 그러면 em.gerReference()로 가져온 객체의 정체는 뭐냐?
    • class가 Member가 아니라 Hibernate에서 만든 Proxy 객체이다.
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember = " + findMember.getClass());
findMember = class hellojpa.Member$HibernateProxy$5MkyQjYb

 

3. 프록시란


  • 실제 클래스를 상속 받아서 만들어짐.
  • 실제 클래스와 겉 모양이 같음
  • 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면됨 ㅋㅋ
  • 프록시 객체는 실제 객체의 참고(target)을 보관
  • 프록시 객체를 호출하면 프록시 객체는 실제 객체의 매서드 호출함. 

 

4. 프록시 객체의 초기화


Member member = em.getReference(Member.class, “id1”); 
member.getName()
  • target이 비어있으면 영속성 컨텍스트에 초기화 요청함.
    • 영속성 컨텍스트는 DB에 조회해서 엔티티 생성.
    • target과 진짜 객체가 연결됨
  • 즉 영속성 컨텍스트를 통해 초기화됨.

 

5. 프록시의 특징


  • 프록시 객체는 처음 사용할 때 한 번 만 초기화됨.
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아님,
    초기화되면 프록시 객체를 통해서~ 실제 엔티티에 접근이 가능함.
  • 프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 주의해야해.
    - 타입이 달라, 따라서 ==비교하면 실패하니, instance of를 사용해야해)
    - hibernate가 실제 객체를 상속받아 proxy객체로 만듦. 
    - class hellojpa.Member$HibernateProxy$5MkyQjYb 이런식임.
  • 영속성 컨텍스트에 찾는 엔티티가 이미 있으면, em.getReference()를 호출해도 실제 엔티티 반환.
    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 = " + findMember.getClass());
    
    Member reference = em.getReference(Member.class, member.getId());
    System.out.println("reference.getClass() = " + reference.getClass());
    
    
    tx.commit();
    findMember = class hellojpa.Member
    reference.getClass() = class hellojpa.Member​

    • 왜..? 같지..?
    • 이미 member를 1차 캐시에 올려놨는데 proxy로 가져오는 이점이 없음 그냥 쓰면 됨.
    • JPA에서는 한 Transaction안에서 "=="비교시 PK가 같거나 영속성 컨텍스트에서 가져온 객체를 비교 시 
      항상 TRUE 여야 함.
  • getReference를 두 번 호출하면..? 호출 된 두 proxy는 같을까?
    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());
    
    Member reference = em.getReference(Member.class, member.getId());
    System.out.println("reference.getClass() = " + reference.getClass());
    
    System.out.println("a == a : " + (findMember == reference));
    
    tx.commit();
    같다.
  • findMember = class hellojpa.Member$HibernateProxy$1o8yn3SK reference.getClass() = class hellojpa.Member$HibernateProxy$1o8yn3SK a == a : true
  • 그런데, getReference()하고 .getUserName()을 통해 초기화 된 상태에서
    em.find()를 실행하면..?
    • JPA는 동일 트랜잭션 내에서 ==비교시 PK가 같으면 항상 TRUE를 보장해야함.
    • 그렇다면 처음에 조회한 m1은 proxy 가 분명한데, 나중에 조회한 m2는..?
Member member = new Member();
member.setUserName("hello");
em.persist(member);

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

Member m1 = em.getReference(Member.class, member.getId());
System.out.println("m1 = " + m1.getClass()); //프록시
m1.getUserName(); // proxy 초기화됨.

Member m2 = em.find(Member.class, member.getId());
System.out.println("m2.getClass() = " + m2.getClass()); //실제 객체?

System.out.println("a == a : " + (m1 == m2)); //PK가 같으니 JPA는 ==비교시 항상 TRUE를 보장해야함.

tx.commit();

 

  • 둘 다 같은 프록시가 반환된다.
  • 즉 한번 프록시가 반환되면 이후에 em.find를 해도 프록시를 반환한다.
  • m1 = class hellojpa.Member$HibernateProxy$JsBGTOC7
    m2.getClass() = class hellojpa.Member$HibernateProxy$JsBGTOC7
    a == a : true
  • 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때 프록시를 초기화하면 문제 발생.
    하이버네이트는 org.hibernate.LazyInitializationException 예외를 터트림
    - em.detatch(), em.close, em.clear() 만나는 순간 proxy는 영속성 컨텍스트 도움 못받음.
Member refMember = em.getReference(Member.class, member.getId());
System.out.println("refMember = " + refMember.getClass()); //프록시


//준영속 상태로 바꿔버리면?
em.detach(refMember);

//초기화 영속성 컨텍스트를 통해 진행되어야 하나, 
//준영속 상태는 영속성 컨텍스트에서 관리하지 않기 때문에
//초기화 불가능.
refMember.getUserName();

 

  • 영속성 컨텍스트를 clear해도 proxy 초기화 안됨.
    • 영속성 컨텍스트를 끈 건 아니지만.. 완전히 지우고 새로 만든 것 과 같음.
Member refMember = em.getReference(Member.class, member.getId());
System.out.println("refMember = " + refMember.getClass()); //프록시

em.clear();

refMember.getUserName();
org.hibernate.LazyInitializationException: could not initialize proxy [hellojpa.Member#1] - no Session
  • no Session : 결국 영속성 컨텍스트가 없다, 영속성 컨텍스트에서 관리 안된다느말.
org.hibernate.LazyInitializationException: could not initialize proxy [hellojpa.Member#1] - no Session

 

 

6. 프록시 확인


  • 프록시 인스턴트의 초기화 여부 확인
    • emf.getPersistenceUnitUtil.isLoaded(Object entity)
    • Member refMember = em.getReference(Member.class, member.getId());
      System.out.println("refMember = " + refMember.getClass()); //프록시
      
      //초기화 안한상태.
      System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
    • isLoaded = false
    • Member refMember = em.getReference(Member.class, member.getId());
      System.out.println("refMember = " + refMember.getClass()); //프록시
      
      refMember.getUserName();
      //초기화 상태
      System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(refMember));
    • isLoaded = true
  • 프록시 클래스 확인 방법
    • entity.getClass().getName() 출력(..javasist.. or HibernateProxy…)
  • 프록시 강제 초기화
    • org.hibernate.Hibernate.initialize(entity);
    • Hibernate.initialize(entity);
  • 참고: JPA 표준은 강제 초기화 없음
    • 강제 호출: member.getName()

 

* JPA는 동일 transaction 내에서 동일 PK로 조회한 객체에 대하여 == 비교시 항상 true 보장.

  동일정 보장함.

7. GitHub : 210811 Proxy


 

GitHub - bsh6463/JPA

Contribute to bsh6463/JPA development by creating an account on GitHub.

github.com

 

'인프런 > [인프런] 자바ORM 표준 JPA 프로그래밍' 카테고리의 다른 글

영속성 전이(CASCADE),고아 객체  (0) 2021.08.12
즉시 로딩과 지연 로딩.  (0) 2021.08.12
[예제] 상속관계 매핑  (0) 2021.08.11
@MappedSuperclass  (0) 2021.08.11
상속관계  (0) 2021.08.11
Comments