프록시 (proxy)
프록시 기초
entityManager.find()
: 데이터베이스를 통해서 실제 엔티티 객체 조회
entitiyManager.getReference()
: 데이터베이스 조회를 미루는 가짜 엔티티 객체 조회
프록시 특징
- JPA 내부적으로 실제 엔티티 클래스를 상속 받아서 만들어진다.
- 실제 클래스와 겉 모양이 같다.
- 사용하는 입장에서는 진짜 객체인지 프록시 객체인지 구분하지 않고 사용하면 된다.
- 프록시 객체는 실제 객체의 참조 대상(target)를 보관한다.
- 프록시 객체를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다.
프록시 객체의 초기화
- 프록시 객체는 처음 사용할 때 한번만 초기화 된다.
- 프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것이 아니다. 초기화를 통해서 실제 엔티티에 접근이 가능해진 것이다.
- 프록시 객체는 원본 엔티티를 상속 받는다. 따라서 타입 체크시 == 비교가 아닌, instead of 로 타입을 비교해야 한다.
- 영속성 컨텍스트(1차 캐시)에 이미 찾는 엔티티가 존재하면, em.getReference()를 호출해도 실제 엔티티를 반환한다. (굳이 실객체가 있는제 프록시를 반환할 이유가 없다. 최적화 등의 이유로)
- 영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생한다.
- em.close(), em.detach(entity) 를 하면 더이상 영속성 컨텍스트의 도움을 받을 수 없기 때문에 예외가 발생한다.
- 하이버네이트는
org.hibernate.LazyInitializationException
을 터뜨린다.
Member member = em.getReference(Member.class, "id1");
member.getName();
즉시 로딩과 지연 로딩
지연로딩
- Member와 Team은 다대일 관계
- Member 정보만 필요할 때는 Team 정보가 굳이 필요없는 경우도 있다.
- fetch 속성을 달리 지정해 줌으로써 지연로딩을 할 수 있다.
@ManyToOne(fetch = FetchType.LAZY)
Team team = member.getTeam();
team.getName();
즉시로딩
- 만약 도메인에서 Member와 Team이 항상 함께 사용된다면 지연 로딩을 하는 것보다 즉시로딩을 할 수 도 있다.
@ManyToOne(fetch = FetchType.EAGER)
- 그러나 즉시로딩은 JPQL에서 N+1문제가 발생하는 등 예상하지 못한 SQL이 발생할 수 있다.
- 실무에서는 가급적 모든 연관관계에서 지연 로딩을 사용하는 것이 좋다.
@ManyToOne
, @OneToOne
은 기본이 즉시 로딩 👉 LAZY로 설정
@OneToMany
, @ManyToMany
는 기본이 지연 로딩
참고자료
- 자바 ORM 표준 JPA 프로그래밍, 김영한 저