DeJa
Techvu
DeJa
전체 방문자
오늘
어제
  • Techvu (60)
    • DesignPatterns (3)
      • 생성 (0)
      • 구조 (1)
      • 행동 (2)
    • Refactoring (0)
    • DataStructures (0)
    • Algorithms (24)
      • 기본 지식 (12)
      • 문제 풀이 (12)
    • OOP (0)
    • TDD (2)
    • DDD (0)
    • Programming Languages (9)
      • Java (9)
      • Kotlin (0)
    • Spring (1)
    • JPA (7)
    • Web (1)
      • 기본 지식 (1)
      • 실무 경험 (0)
    • CS (12)
      • Network (1)
      • OS (8)
      • DataBase (3)
      • Server (0)
    • Git (1)
    • Conferences (0)

블로그 메뉴

  • 홈
  • 태그
  • 미디어로그
  • 위치로그
  • 방명록

공지사항

  • Study
  • GitHub
  • Medium Blog

인기 글

태그

  • DATABASE
  • CS
  • 디자인패턴
  • OS
  • 알고리즘
  • java
  • network
  • Spring
  • web
  • JPA
  • TDD

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
DeJa

Techvu

프록시 객체와 영속성 컨텍스트
JPA

프록시 객체와 영속성 컨텍스트

2022. 1. 5. 19:56
728x90

프록시 객체와 영속성 컨텍스트

JPA 의 프록시 객체가 어떻게 초기화 되고, getReference() 를 사용할 때, 영속성 컨텍스트에 값이 존재하는 경우와 하지 않는 경우에는 어떻게 동작하는지 배워보자.

em.find vs em.getReference

em.find

// em.find() 시점에 조회 쿼리가 나간다.
// findMember : Member@9361
Member findMember = em.find(Member.class, member.getId());
System.out.println("username : " + findMember.getUsername());
System.out.println("id : " + findMember.getId());

아래는 조회 쿼리이다.

select
  member0_.member_id as member_i1_2_0_,
  member0_.createdBy as createdb2_2_0_,
  member0_.createdDate as createdd3_2_0_,
  member0_.lastModifiedBy as lastmodi4_2_0_,
  member0_.lastModifiedDate as lastmodi5_2_0_,
  member0_.city as city6_2_0_,
  member0_.street as street7_2_0_,
  member0_.team_id as team_id10_2_0_,
  member0_.name as name8_2_0_,
  member0_.zipcode as zipcode9_2_0_,
  team1_.team_id as team_id1_5_1_,
  team1_.createdBy as createdb2_5_1_,
  team1_.createdDate as createdd3_5_1_,
  team1_.lastModifiedBy as lastmodi4_5_1_,
  team1_.lastModifiedDate as lastmodi5_5_1_,
  team1_.name as name6_5_1_ 
from
    member member0_ 
left outer join
    team team1_ 
        on member0_.team_id=team1_.team_id 
where
    member0_.member_id=?

em.getReference

// em.getReference 시점에는 쿼리가 안나간다.
// findMember: Member$HibernateProxy$U7SsWYXV@9342
Member findMember = em.getReference(Member.class, member.getId());
// ID 는 위에서 이미 파라미터로 넘겨서 담아줬기 때문에 조회할때 쿼리가 나가지 않는다.
System.out.println("id : " + findMember.getId());
// ID 를 제외한 다른 속성을 조회할때 쿼리가 나간다.
System.out.println("username : " + findMember.getUsername());

프록시 객체의 초기화

✔ getUsername() 을 호출한다.
✔ 프록시 객체의 target 에 값이 없을때, 영속성 컨텍스트를 통해서 초기화를 요청한다.
✔ 영속성 컨테스트는 DB 에서 값을 조회해와서 실제 Entity 를 생성하고, Proxy 객체의 target 변수에 실제 Entity 를 연결한다.
✔ 프록시 객체는 딱 한 번만 초기화 되고 나서 target 을 통해 실제 Entity 의 값을 조회한다.

특징

✔ 프록시 객체는 처음 사용할 때 한 번만 초기화
✔ 초기화 이후에는 프록시 객체를 통해서 실제 엔티티에 접근 가능
✔ 프록시 객체는 원본 엔티티를 상속 받음, 따라서 타임 체크시 주의(instanceof 사용, == 비교 X )
✔ 찾고자 하는 엔티티가 영속성 컨텍스트에 이미 있으면, em.getReference() 를 호출해도 실제 엔티티 반환
✔ 준영속 상태일 때, 프록시를 초기화 하면 문제 발생(하이버네이트는 org.hibernate.LazyInitializationException 예외 발생)

Example 1

tx.begin();
Member member = new Member();
member.setUsername("Member1");
em.persist(member);

em.flush(); // 영속성 컨텍스트의 변경 내용을 DB 에 동기화
em.clear(); // 영속성 컨텍스트를 비워줌으로써 준영속 상태가 된다.

Member findMember1 = em.find(Member.class, member.getId());
System.out.println("findMember1 : " + findMember1.getClass());

Member findMember2 = em.getReference(Member.class, member.getId());
System.out.println("findMember2 : " + findMember2.getClass());
tx.commit();

findMember1 과 findMember2 의 결과를 예측해보자.

findMember1 은 DB 에서 데이터를 조회하기 때문에, 실제 엔티티이다. findMember2 는 getReference() 로 조회했지만 실제 엔티티이다.

findMember1 : class com.jtcwp.purejpa.domain.Member
findMember2 : class com.jtcwp.purejpa.domain.Member

✔ 첫 번째 이유는 findMember1 조회 이후 이미 영속성 컨텍스트에 엔티티가 존재하기 때문에 프록시 객체를 만들어줄 이유가 없다.
✔ 두 번째 이유는 한 트랜잭션 내에서 같은 엔티티를 조회했을 때, 완전 동일한 엔티티를 반환하고자 하는 JPA 매커니즘 때문이다.

만약에, findMember1 조회 이후, 영속성 컨텍스트에서 조회했는데 findMember1 과 다른 객체가 조회된다고하면 이상할 것이다.

Example 2

tx.begin();
Member member = new Member();
member.setUsername("Member1");
em.persist(member);

em.flush(); // 영속성 컨텍스트의 변경 내용을 DB 에 동기화
em.clear(); // 영속성 컨텍스트를 비워줌으로써 준영속 상태가 된다.

Member findMember1 = em.getReference(Member.class, member.getId());
System.out.println("findMember1 : " + findMember1.getClass());

Member findMember2 = em.getReference(Member.class, member.getId());
System.out.println("findMember2 : " + findMember2.getClass());
tx.commit();

위 결과는 쉽게 예상할 수 있을 것이다.

findMember1 : class com.jtcwp.purejpa.domain.Member$HibernateProxy$s25LIy0W
findMember2 : class com.jtcwp.purejpa.domain.Member$HibernateProxy$s25LIy0W

Example 3

tx.begin();
Member member = new Member();
member.setUsername("Member1");
em.persist(member);

em.flush(); // 영속성 컨텍스트의 변경 내용을 DB 에 동기화
em.clear(); // 영속성 컨텍스트를 비워줌으로써 준영속 상태가 된다.

Member findMember1 = em.getReference(Member.class, member.getId());
System.out.println("findMember1 : " + findMember1.getClass());

Member findMember2 = em.find(Member.class, member.getId());
System.out.println("findMember2 : " + findMember2.getClass());

System.out.println(findMember1 == findMember2);
tx.commit();

findMember1 을 조회할 때는 쿼리가 나가지 않는다. (여기까지는 알고 있을 것이다.) findMember2 의 결과가 어떻게 될까 예측해보자.

find 를 하니까 일단 조회 쿼리가 나갈것이고, findMember2 는 실제 엔티티일까? 프록시 객체일까?

findMember1 : class com.jtcwp.purejpa.domain.Member$HibernateProxy$Im1r4Tbc
findMember2 : class com.jtcwp.purejpa.domain.Member$HibernateProxy$Im1r4Tbc
true

findMember2 도 프록시 객체가 반환된다. 프록시를 한번 조회하면 두 번째 조회 할때 em.find() 나 em.getReference() 둘 중 아무거나 사용해도 프록시를 사용한다.

어쨋든 핵심은 한 트랜잭션 내에서 같은 엔티티를 조회했을 때, 완전 동일한 엔티티를 반환하고자 하는 JPA 매커니즘 때문에 프록시 객체든 실제 엔티티든 크게 신경쓰지 않고 사용할 수 있는 것이다.

References

  • 자바 ORM 표준 JPA 프로그래밍
728x90
저작자표시 비영리 동일조건 (새창열림)

'JPA' 카테고리의 다른 글

다양한 연관관계 매핑과 설정에 따른 트레이드 오프  (0) 2021.12.31
테이블과 컬럼에 대한 명세를 엔티티에 자세하게 적는것이 좋은지?  (0) 2021.12.29
AllocationSize 를 통한 성능 최적화  (0) 2021.12.28
기본키 매핑 전략에 따른 INSERT QUERY 실행 시점  (0) 2021.12.28
EntityManager 를 쓰레드간 공유하면 안되는 이유  (0) 2021.12.28
    'JPA' 카테고리의 다른 글
    • 다양한 연관관계 매핑과 설정에 따른 트레이드 오프
    • 테이블과 컬럼에 대한 명세를 엔티티에 자세하게 적는것이 좋은지?
    • AllocationSize 를 통한 성능 최적화
    • 기본키 매핑 전략에 따른 INSERT QUERY 실행 시점
    DeJa
    DeJa
    Tech Blog

    티스토리툴바