프록시 객체와 영속성 컨텍스트
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
'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 |