해당 글은 김영한 님의 자바 ORM 표준 JPA 프로그래밍을 참고하여 작성한 글입니다.
✅ 프록시
기능을 구현할 때 DB에서 가져오고 싶은 정보의 범위가 비즈니스 로직에 따라 다르다. 예를 들어, Member와 Team이라는 두 개의 entity가 연관관계상에 있을 때, 구현 목적에 따라 두 개의 데이터가 한 번에 조회되는 것이 좋을 수도 있고, 그렇지 않을 수도 있다. 이러한 상황을 JPA가 프록시와 지연로딩을 통해 해결한다. 이 글에서는 우선 프록시에 대해 알아보겠다.
▶ 프록시 기초
1️⃣ em.find()
- DB를 통해 실제 Entity 객체를 조회하는 메서드
- member라는 객체를 사용하지 않고 find()만 해도 SELECT 쿼리를 실행한다.
2️⃣ em.getReference()
- DB 조회를 미루는 가짜(프록시) Entity 객체를 조회하는 메서드, 이 가짜 객체를 프록시 객체라고 부른다.
- find()와 반대로 SELECT 쿼리가 실행되지 않는다.
- 이 메서드를 호출할 때 JPA는 DB를 조회하지 않고 실제 Entity 객체도 생성하지 않는다. 대신 DB 접근을 위임한 프록시 객체를 반환한다.
▶ 프록시 특징
- 프록시 클래스는 실제 클래스를 상속받아서 만들어지므로 실제 클래스와 겉모습이 같다.
- 프록시를 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용한다.
- 프록시 객체는 실제 객체의 참조(target)를 보관한다.
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메서드를 호출한다.
- getId()를 호출하면 target의 getId()를 대신 호출한다.
▶ 프록시 객체의 초기화
Member member = em.getReference(Member.class, "id1");
member.getName(); //프록시 객체의 초기화
프록시 객체는 member.getName()처럼 실제 사용될 때 DB를 조회해서 실제 Entity 객체를 생성하는데 이를 프록시 객체의 초기화라고 한다. (target에 값이 없는 상태일 때)
- 프록시의 초기화 과정
- 프록시 객체에 member.getName()을 호출해서 실제 데이터를 조회한다.
- 프록시 객체는 실제 Entity가 생성되어 있지 않으면 영속성 컨텍스트에서 실제 Entity 생성을 요청하는데 이것을 초기화라 한다.
- 영속성 컨텍스트는 DB를 조회하여 실제 Entity 객체를 생성한다.
- 프록시 객체는 생성된 실제 Entity 객체의 참조를 Member target 멤버변수에 보관한다.
- 프록시 객체는 실제 Entity 객체의 getName()을 호출해서 결과를 반환한다.
즉, 위 코드에서 getName()을 호출하는 시점에 영속성 컨텍스트로 Member을 요청하여 실제 참조를 가지게 된다. 이후 다시 getName()을 재요청한다면 영속성 컨텍스트에서 값을 받아왔기 때문에 프록시에서 조회한다.
▶ 프록시 주의사항
- 프록시 객체는 처음 사용할 때 한 번만 초기화된다.
- 프록시 객체를 초기화한다고 프록시 객체가 실제 Entity로 바뀌는 것은 아니다. 프록시 객체가 초기화되면 프록시 객체를 통해서 실제 엔티티에 접근이 가능해질 뿐이다.
- 프록시 객체는 원본 Entity를 상속받은 객체이므로 타입 체크 시에 주의해서 사용해야 한다. (== 비교가 아닌, instance of 비교 사용)
- 영속성 컨텍스트에 찾는 Entity가 이미 있으면 DB를 조회할 필요가 없으므로 em.getReference()를 호출해도 프록시가 아닌 실제 Entity를 반환한다.
- 초기화는 영속성 컨텍스트의 도움을 받아야 가능하다. 따라서 영속성 컨텍스트에서 관리하지 않는 준영속 상태의 프록시를 초기화하면 문제가 발생한다.
- 하이버네이트는 org.hibernate.LazyInitializationException 예외를 발생시킨다.
▶ 프록시 유틸리티 메서드
- 프록시 인스턴스의 초기화 여부 확인: PersistenceUtilUtil.isLoaded(Object entity)
- 프록시 클래스 확인 방법: entity.getClass().getName()
- 프록시 강제 초기화
- JPA 표준에는 강제 초기화가 없다. member.getName()처럼 프록시의 메서드를 직접 호출해야 한다.
- 하이버네이트가 제공하는 org.hibernate.Hibernate.initialize(entity)을 통해 가능하다.
📍 참고
'Programming > JPA' 카테고리의 다른 글
[JPA] 영속성 전이(CASCADE)와 고아 객체 (1) | 2024.07.25 |
---|---|
[JPA] 즉시 로딩과 지연 로딩 (0) | 2024.07.25 |
[JPA] 고급 매핑(상속관계 매핑) - 엔티티(Entity) 매핑 (7) (0) | 2024.07.25 |
[JPA] 다양한 연관관계 매핑 - 엔티티(Entity) 매핑 (6) (1) | 2024.07.25 |
[JPA] 단방향 연관관계와 양방향 연관관계 - 엔티티(Entity) 매핑 (5) (1) | 2024.07.25 |