(JPA)프록시
프록시
프록시 기초
- em.find() vs em.getReference()
- em.find(): 데이터베이스를 통해서 실제 엔티티 객체를 조회
- em.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) 엔티티 객체 조회
프록시 특징
- 실제 클래스를 상속 받아서 만들어진다.
- 실제 클래스와 겉 모양이 같다.
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.(이론상)
- 프록시 객체는 실제 객체의 참조(target)를 보관
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메서드를 호출한다.
프록시 객체의 초기화
1
2
Member member = em.getReference(Member.class, "id1");
member.getName();
- getName()
- 초기화 요청
- DB조회
- 실제 Entity 생성
- 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차 캐시안에 실제 Member Entity가 있는데, 굳이 프록시를 설정해서 사용하는것은 이점이 없기 때문이다.
- 같은 트랜젝션 안에서 같은 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.