프록시
프록시를 사용하면 객체를 처음부터 DB에서 조회하는 것이 아니라
실제 사용하는 시점에 DB에서 조회하는 것이다.(= 지연 로딩)
예를 들어 Member와 Team이 연관관계 매핑이 되어있다.
그러면 find를 통하여 Member를 찾을 때, join 쿼리를 이용하여 Team도 당연히 조회가 된다.
이 부분이 효율적이지 못해 프록시를 사용한다면
Member를 찾을 때, DB에서 Member만을 조회하여 조회할 수 있다.
getReference & find
우리는 엔티티를 조회할 때 em.find를 사용해왔다.
find는 위와 같은 실제 엔티티를 반환하게 된다.
하지만 em.getReference는 프록시 객체를 가져온다.
Member findMember = em.getReference(Member.class, member.getId());
해당 코드를 입력하고 나서의 결과창이다.
보면 데이터베이스에 쿼리가 나가지 않는다.
왜?
현재 Member 객체인 findMember를 실제로 사용하지 않았기 때문에 DB에서 조회하지 않는다.
여기서 member.getId()를 쓰지 않았냐 라고 말할 수 있다.
하지만 id는 findMember를 찾기 위해 이미 id값을 넣었기 때문에
값이 있는 것으로 생각되어 그냥 가져올 수 있다고 한다.
Member findMember = em.getReference(Member.class, member.getId());
System.out.println("findMember = " + findMember.getId());
System.out.println("findMember = " + findMember.getName());
증명하기 위해 위와 같은 코드를 작성한다.
만약 id를 찾기 위해 프록시 객체가 생성되었다면
findMember.getId() 의 출력문 다음에 DB를 select하는 쿼리가 나가야한다.
하지만 id는 그대로 나오고 getName이 호출될 때, select 쿼리가 나가게 된다.
따라서 JPA에서는 id는 이미 있어서 지나치고,
getName에서 "어 이건 없네" 하면서 디비에 쿼리를 날려 조회를 하게 된다.
위에서 findMember는 뭘까?
System.out.println("findMember = " + findMember.getClass());
결과값은
findMember = class hellojpa.Member$HibernateProxy$kyWww7UQ 이다.
실제 Member 클래스가 아니라 Member의 hibernate의 proxy이다.
즉, 하이버네이트가 강제로 만든 가짜 클래스 -> 프록시 클래스 이다.
프록시 초기화
아마 프록시를 공부하면서 가장 중요한 그림인 것 같다.
1. getName() -> 실제 객체를 사용할 때
2. 영속성 컨텍스트에 해당 객체가 있는지 조회
3. 영속성 컨텍스트에 없다면 DB에서 조회
4. 조회한 엔티티를 실제로 생성
5. 프록시는 실제 객체를 상속 받아서 만들어진 것으로
Proxy에 있는 target이 처음엔 값이 없지만 실제 객체를 가르키게 됨.
즉, getName()이 호출되면서 실제 객체를 사용하여 프록시 객체가 초기화 되는 것을 알 수 있다.
프록시 특징
신기하게 두 번 호출하게 도면 두 번째에서는 db에 쿼리를 날리지 않는다.
이 부분은 영속성 컨텍스트에서 1차 캐시의 개념과 똑같은 것 같다.
프록시는 초기화 된다고 해서 실제 엔티티로 바뀌는 것이 아니다.
(초기화 -> 실제 클래스와 연결된 것)
보면 class 명이 똑같은 것을 알 수 있다.
타입 체크 시, ==이 아닌 instance of를 사용해야한다.
instance of는 상속 관계인지 확인하는 메서드이다.
== 사용 예시
false가 나옴
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()));
당연히 true 가 나온다.
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()));
instance of 사용 예시
Member m1 = em.find(Member.class, member1.getId());
Member m2 = em.getReference(Member.class, member2.getId());
System.out.println("m1 == m2: " + (m1 instanceof Member));
System.out.println("m1 == m2: " + (m2 instanceof Member));
영속성 컨텍스트에 실제 엔티티가 있으면 getReference를 해도 엔티티가 나온다.
Member m1 = em.find(Member.class, member1.getId());
System.out.println("m1.getClass() = " + m1.getClass());
Member m1Reference = em.getReference(Member.class, member1.getId());
System.out.println("m1Reference.getClass() = " + m1Reference.getClass());
앞서 현 강의의 영속성 컨텍스트 단원에서
영속성 컨텍스트의 이점은 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩이 있다.
동일성 보장 : == 비교에 대해 같은 트랜잭션 레벨, 영속성 컨텍스트 안에서 조회하면 항상 성립한다.
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember.getClass() = " + refMember.getClass());
Member findMember = em.find(Member.class, member1.getId());
System.out.println("findMember.getClass() = " + findMember.getClass());
System.out.println("refMember == findMember:" + (refMember == findMember));
만약 위와 같은 상황이면 값이 어떻게 될까?
처음 나의 생각
-> refMember는 프록시이고, findMember는 당연히 실제 엔티티라고 생각
하지만 틀렸다.
왜?
프록시로 한 번 조회하면 em.find도 프록시를 반환해버린다.
그래야지 동일성 보장이 되기 때문이다.
핵심은 개발할 때 이게 프록시든 아니든 크게 문제없이 개발하는 것이라고 한다.
만약 중간에 영속성 컨텍스트를 끊게 된다면(= 준영속 상태) 어떻게 될까?
Member member1 = new Member();
member1.setName("member1");
em.persist(member1);
em.flush();
em.clear();
Member refMember = em.getReference(Member.class, member1.getId());
System.out.println("refMember.getClass() = " + refMember.getClass()); //Proxy
em.detach(refMember);
System.out.println(refMember.getName());
detach로 refMember는 준영속 상태가 되었다.
이 때, getName을 호출하게 된다면 어떻게 될까?
detach는 준영속 상태로 영속성 컨텍스트에서 관리를 안한다는 뜻이다.
위와 같은 예외가 발생한다.
could not initialize proxy -> 영속성 컨텍스트가 없다는 얘기이다.
이건 강의와 별개 없는 얘기지만
catch에 e.printStackTrace()를 작성하지 않으면 위와 같은 예외가 출력되지 않는다.
Reference
자바 ORM 표준 프로그래밍 (김영한 지음)
'Spring 강의 > JPA - 기본편' 카테고리의 다른 글
값 타입 (0) | 2021.12.12 |
---|---|
프록시와 연관관계 관리(2) 즉시 로딩과 지연 로딩 ~ 영속성 전이 (0) | 2021.12.10 |
고급 매핑 (0) | 2021.12.07 |
다양한 연관관계 매핑 (0) | 2021.12.02 |
연관관계 매핑 기초 (0) | 2021.11.29 |