Member와 Team이 연관관계로 매핑이 되어있는 상황을 예시로 생각해 보자
비즈니스 로직에 따라 Member를 조회하고 자주 Team을 찾는 경우, Member만 사용을 많이 하는 경우가 있다.
각 경우에 따라 전자는 Team까지 같이 가져오는 SQL을 하는 것이 후자는 Member만 가져오는 SQL을 작성하는 것이 유리하다.
이를 해결하지위해 JPA는 지연로딩과 프록시를 사용한다.
프록시
em.find() vs em.getReference()
- em.find : 데이터베이스를 통해서 실제 엔티티 객체를 조회
- Member와 Team 테이블을 Join해서 가져온다
- em.getReference() : 데이터베이스 조회를 미루는 가짜(프락시) 엔티티 객체 조회
- getReference하는 시점에는 DB에 쿼리가 나가지 않는다.
- getReference한 값이 사용되는 시점에 DB에 쿼리를 보낸다.
- getReference를 해서 얻은 인스턴스의 class를 조회하면 프록시 클래스로 나온다.
getReference()를 하면 하이버네이트가 라이브러리로 Proxy 엔티티 객체를 준다.
target이 진짜 객체를 가리킨다.
프록시의 특징
- 실제 클래스를 상속받아서 만들어진다.
- 실제 클래스와 겉모양이 같다
- 사용하는 입장에서 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.
- 프록시 객체는 실제 객체의 참조(target)을 보관한다.
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
Member member = em.getReference(Member.class, "id");
member.getName();
위 코드가 수행되는 과정을 살펴보자
- getReference를 하면 프록시 객체를 얻는다.
- getName()을 호출하면 초기에 프록시의 target의 값이 비어있으므로 JPA가 영속성 컨텍스트에 초기화 요청을 한다.
- 영속성 컨텍스트가 DB에서 조회를 해서 실제 Entity객체를 생성해서 준다.
- target과 실제 Entity 객체를 연결시켜준다.
- target의 진짜 getName()을 통해서 Member의 Name이 반환된다.
- 이후에 요청은 초기화가 된 상태이므로 초기화가 더 이상 필요 없다.
프록시의 특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화하면 된다.
- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것이 아니라 초기화가 되면 프록시 객체를 통해 실제 엔티티에 접근이 가능하다.
- 프록시 객체는 원본 엔티티를 상속받는다, 따라서 타입 체크 시 주의해야 한다. (==비교 대신, instance of사용 )
- 영속성 컨텍스트에 찾는 엔티티가 이미 있으면 em.getReference()를 호출해도 실제 엔티티를 반환한다.
- case1
- Member member = em.find(Member.Class, "1");
- Member reference = em.getReference(Member.Class, "1");
- member와 reference의 class타입이 동일하다. 즉 reference는 프록시가 아니라 실제 엔티티이다.
- case2
- JPA는 한 트렌잭션 안에서 같은 Id로 조회한 두 인스턴스는 ==을 보장해 준다.
- Member refMember = em.getReference(Member.Class, "1");
- Member findMember = em.find(Member.Class, "1");
- 두 코드가 순서대로 한 트렌잭션 안에서 실행되었으면 JPA는 ==을 보장해 주기 위해 findMember도 Proxy타입으로 반환된다.
- 영속성 컨텍스트에 도움을 받을 수 없는 준영속 상태일 때 프록시를 초기화 하면 문제가 발생한다.
- Member refMember = em.getReference(Member.Class, "1");
- em.clear() , em.detach(refMember), em.close()
- refMember.getName();
- could not initialize proxy예외가 발생한다.
프록시 확인 메서드
- 프록시 인스턴스의 초기화 여부 확인
- emf.gerPersistenceUnitUtil().isLoaded(referMember);
- 프록시 클래스 확인 방법
- entity.getClass();
- 프록시 강제 초기화 방법
- Hibernate.initialize(refMember);
'JPA' 카테고리의 다른 글
영속성 전이 : CASCADE, 고아객체 (0) | 2023.08.31 |
---|---|
즉시 로딩과 지연 로딩 (0) | 2023.08.30 |
MappedSuperclass (0) | 2023.08.30 |
상속관계 매핑 (0) | 2023.08.29 |
다양한 연관관계 매핑 (0) | 2023.08.29 |