Post

(JPA)프록시

프록시

프록시 기초

  • em.find() vs em.getReference()
  • em.find(): 데이터베이스를 통해서 실제 엔티티 객체를 조회
  • em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회

프록시 특징

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

프록시 객체의 초기화

1
2
Member member = em.getReference(Member.class, "id1");
member.getName();
  1. getName()
  2. 초기화 요청
  3. DB조회
  4. 실제 Entity 생성
  5. target.getName()
    • 영속성 컨텍스트를 통해 프록시에 값이 없을 경우 초기화를 요청하고, DB를 조회한 후 실제 Entity를 생성해준다.
    • getName()을 두번 연속 요청할 경우에는 첫번째 요청에서만 초기화를 요청하고, 이후 요청에서는 생성된 Entity를 사용하여 초기화가 진행되지 않는다.

중간 정리

  • 프록시 객체는 처음 사용할 때 한번 만 초기화가 진행된다.
  • 프록시 객체를 초기화 할 때, 프록시 객체가 실제 Entity로 바뀌는 것이 아니라 초기화 되면 프록시 객체를 통해 실제 Entity에 접근이 가능해진다.
  • 프록시 객체는 원본 Entity를 상속받으며, 따라서 타입 체크 시 주의를 해야한다.(== 비교 대신, instance of 를 사용)
  • 영속성 컨텍스트에 이미 찾는 엔티티가 있으면, em.getReference() 를 호출해도 실제 Entity가 반환된다.
  • 영속성 컨텍스트의 도움을 받을 수 없는 준 영속 상태일때는 프록시를 초기화 하면 문제가 발생한다.
    • 하이버네이트는 org.hibernate.LazyInitializationException 예외를 발생시킨다.

프록시 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);

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

Member realMember = em.find(Member.class, member1.getId());
System.out.println("realMember = " + realMember.getClass()); // 첫번째 클래스 출력

Member proxyMember = em.getReference(Member.class, member1.getId());
System.out.println("proxyMember = " + proxyMember.getClass()); // 두번째 클래스 출력

System.out.println("realMember == proxyMember: " + (realMember == proxyMember)); // true? false?
  • 첫번째 클래스 출력에서 실제 Member Entity가 출력되며, 두번째 출력 또한 프록시 객체가 출력되는 것이 아니라 실제 Member Entity가 출력된다.
  • 그냥 보기에는 false 가 될것 같지만, ture 가 정답이다.
  • 위에 처럼 동작되는 이유로는
    1. 1차 캐시안에 실제 Member Entity가 있는데, 굳이 프록시를 설정해서 사용하는것은 이점이 없기 때문이다.
    2. 같은 트랜젝션 안에서 같은 PK를 가지고 있는 객체 간의 == 비교는 true 가 반환되도록 매커니즘 설계 되어 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Member member1 = new Member();
member1.setUsername("member1");
em.persist(member1);

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

Member proxyMember = em.getReference(Member.class, member1.getId());
System.out.println("proxyMember = " + proxyMember.getClass()); // 첫번째 클래스 출력

Member realMember = em.find(Member.class, member1.getId());
System.out.println("realMember = " + realMember.getClass()); // 두번째 클래스 출력

System.out.println("realMember == proxyMember: " + (realMember == proxyMember)); // true? false?
  • 첫번째 클래스 출력에서는 당연히 Proxy가 나올것이고, 두번째 출력에서는 getReference() 를 사용한 조회가 아니기에 실제 Entity가 나올것이다.
  • 하지만 JPA는 같은 트랜젝션 안에서 == 비교는 true 를 반환해야 하는데, 위에 같이 다르게 나올경우 true 를 반환할 수 없게 된다.
  • 그렇기 때문에 두번째 클래스 출력 또한 Proxy 가 출력되게 된다.

프록시 확인

프록시 인스턴스의 초기화 여부 확인

1
2
3
4
5
6
EntityManagetFactory emf = Persistence.createEntityManagerFactory("hello");

Member proxyMember = em.getReference(Member.class, member1.getId());
System.out.println("proxyMember = " + proxyMember.getClass());

System.out.println("isLoaded = " + emf.getPersistenceUnitUtil().isLoaded(proxyMember));
  • PersistenceUtil.isLoaded(Object entity)
  • 초기화 된 경우 true 반환 아닌 경우 false 반환

프록시 클래스 확인 방법

1
System.out.println("proxyMember = " + proxyMember.getClass());
  • 그냥 단순하게 찍어보는 방법 밖에 없다.

프록시 강제 초기화

1
Hibernate.initialize(proxyMember); // 강제 초기화
  • Hibernate가 제공

  • JPA 표준은 강제 초기화가 없다.


REFERENCE


#JPA_프록시

This post is licensed under CC BY 4.0 by the author.