프록시 ?
객체는 객체 그래프로 연관된 객체들을 탐색한다.
그런데 객체가 DB 에 저장되어 있으므로 연관된 객체를 마음껏 탐색 하기는 어렵다.
JPA 구현체들은 이 문제를 해결하려고 프록시라는 기술을 사용한다.
프록시를 사용하면 연관된 객체를 처음부터 DB에서 조회하는 것이 아니라 실제 사용하는 시점에 DB를 조회할 수 있다.
하지만 자주 함께 사용하는 객체들을 조인을 사용해서 함께 지원하는 것이 효과적이다.
✅ 프록시 객체?
지연로딩 기능을 사용하려면 실제 엔티티 객체 대신에 DB 조회를 지연할 수 있는 가짜 객체가 필요한데
이것을 프록시 객체라고 한다.
✅ 프록시 기초
JPA 에서 식별자로 엔티티 하나를 조회할 때는 EntityManager.find() 를 사용한다.
이 메소드는 영속성 컨텍스트에 엔티티가 없으면 db를 조회한다.
이렇게 엔티티를 직접 조회하면 조회한 엔티티를 실제 사용하든 사용하지 않든 DB를 조회하게 된다.
Member member = em.find( Member.class, "binsoo" )
엔티티를 실제 사용하는 시점까지 미루고 싶다면 EntitManager.getReference() 를 사용하면 된다.
이 메소드를 호출할 때 JPA 는 DB 를 조회하지 않고 실제 엔티티 객체도 생성하지 않는다.
대신에 DB 접근을 위임한 프록시 객체를 반환한다.
EntitManager.getReference( Member.class, "binsoo" )
✅ 프록시 특징
프록시 클래스는 실제 클래스를 상속 받아서 만들어지므로 실제 클래스와 겉모습이 같다.
따라서 사용하는 입장에서는 이것이 진짜 객체인지 프록시 객체인지 구분하지 않고 사용할 수 있다.
프록시 객체는 실제 객체에 대한 참조를 보관한다.
그리고 프록시 객체의 메소드를 호출하면 객체는 실제 객체의 메소드를 호출한다.
- 프록시 객체는 처음 사용할 때 한 번만 초기화 된다.
- 프록시 객체를 초기화 한다고 프록시 객체가 실제 엔티티로 바뀌는 것은 아니다.
- 프록시 객체가 초기화 되면 프록시 객체를 통해서 실제 엔티티에 접근할 수 있다.
- 프록시 객체는 원본 엔티티를 상속 받은 객체 이므로 타입 체크 시에 주의해서 사용해야 한다.
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 DB를 조회할 필요가 없으므로 em.getReference () 를 호출해도 프록시가 아닌 실제 엔티티를 반환한다.
- 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태의 프록시를 초기화하면 문제가 발생한다. 하이버네이트는 LazyInitializationException 시킨다.
준영속 상태의 프록시를 초기화
아래 코드는 em.close() 메소드로 영속성 컨텍스트를 종료해서
member는 준영속 상태이다.
mamber.getName() 을 호출하면 프록시를 초기화 해야 하는데
영속성 컨텍스트가 없으므로 실제 엔티티를 조회 할 수 없다.
따라서 LazyInitializationException 가 발생한다.
Member member = em.getReference ( Member.class , "binsoo" )
transaction.commit()
em.close()
member.getName()
✅ 프록시 객체의 초기화
프록시객체는 member.getName() 처럼 실제 사용될 때 DB를 조회해서 실제 엔티티 객체를 생성하는데
이것을 객체의 초기화라 한다.
- 프록시 객체에 member.getName () 을 호출해서 실제 데이터를 조회한다.
- 프록시 객체는 실제 엔티티가 생성되어 있지 않으면 영속성컨텍스트에 실제 엔티티 생성을 요청하는데 이것을 초기화라 한다.
- 영속성 컨텍스트는 DB를 조회해서 실제 엔티티 객체를 생성한다.
- 프록시 객체는 생성된 실제 엔티티 객체의 참조를 Member target 멤버 변수에 보관한다.
- 프록시 객체는 실제 엔티티 객체의 getName() 을 호출해서 결과를 반환한다.
// 1. getName()
Member member = em.getReference( Member.class, "binsoo" )
member .getName()
class MemberProxy extends Member {
Member target = null
public String getName (){
if( target == null ){
// 2. 초기회 요청
// 3. DB 조회
// 4. 실제 엔티티 생성 및 참조 보관
}
// 5. target.getName()
return target.getName();
}
}
✅ 프록시와 식별자
엔티티를 프록시로 조회할 때 식별자 값을 파라메터로 전달하는데 프록시 객체는 이 식별자 값을 보관한다.
- 식별자 보관
Team team = em.getReference ( Team.class, "binsoo" )
- 초기화 되지 않음
team.getId()
프록시 객체는 식별자 값을 가지고 있으므로
식별자 값을 조회하는 team.getId 를 호출해도 프록시를 초기화 하지 않는다.
@Access(AccessType.PROPERTY)
엔티티 접근 방식을 프로퍼티로 설정한 경우에만 초기화 하지 않는다.
@Access(AccessType.FILED)
엔티티 접근 방식을 필드로 설정하면 JPA는 GETiD 메소드가 ID 만 조회하는 메소드 인지
다른 필드까지 활용해서 어떤 일을 하는 메소드 인지 알지 못하므로 프록시 객체를 초기화 한다.
프록시는 아래 코드 처럼 연관관계를 설정할 때 유용하게 사용할 수 있다.
연관관계를 설정할 떄는 식별자 값만 사용함로 프록시를 사용하면 DB 접근 횟수를 줄일 수 있다.
연관관계를 설정 할 때는 엔티티 접근 방식을 필드로 설정해도 프록시를 초기화 하지 않는다.
Member member = em.find( Member.class , "bin" )
- SQL 실행 하지 않음
Team team = em.getReference ( Team.class, "binsoo" )
member.setTeam(team)
✅ 프록시 확인
JPA 가 제공하는 PersistenceUnitUtil.isLoaded ( Object object ) 메소드를 사용하면
프록시 인스턴스의 초기화 여부를 확인 할 수 있다.
아직 초기화 되지 않은 프록시 인스턴스는 false 를 반환한다.
이미 초기화 되었거나 프록시 인스턴스거 아니면 true 를 반환한다.
- 초기화 여부 확인
boolean isLoad = em.getEntityManagerFactory()
.getOersistenceUnitUtil()
.isLoaded(entity);
조회한 엔티티가 진짜 엔티티인지 프록시로 조회한 것인지 확인 하려면 클래스명을 직접 출력해보면 된다.
✅ 프록시 강제 초기화
하이버네이트의 initialize() 메소드를 사용하면 프록시를 강제로 초기화 할 수 있다.
--프록시 초기화
org.hibenate.Hibernate.initialize ( order.getMember() )
JPA 표준에는 프록시 강제 초기화 메소드가 없다.
따라서 강제로 초기화 하려면 member.getName() 처럼 프록시를 직접 호출하는 방법이 존재 한다.
JPA 표준에는 단지 초기화 여부만 확인할 수 있다.
글이 도움이 되었다면 Click ! ✨
https://www.youtube.com/watch?v=cvGc4y-iSfA&t=1s
'개발중 > Java Persistence API (JPA)' 카테고리의 다른 글
JPA - 값 타입 컬렉션 (0) | 2021.06.30 |
---|---|
JPA - 즉시 로딩과 지연 로딩 (0) | 2021.06.28 |
식별 관계와 비식별 관계 구분 (0) | 2021.06.25 |
JPA - 어노테이션 정리 (0) | 2021.06.25 |
JPA / 객체 간 연관관계 매핑 - 기록 (0) | 2021.06.25 |