객체의 경우 연관된 객체들을 자유롭게 탐색할 수 있다. 하지만 데이터베이스의 경우 연관된 테이블을 조회하려면 JOIN을 해야하기 때문이다.
이는 비용이 발생하며, 만약 연관된 테이블의 데이터를 사용하지 않는다면 더욱 더 낭비가 된다.
따라서 해당 문제를 해결하기 위해 프록시가 등장하였다.
프록시는 연관된 객체를 처음부터 데이터베이스에서 조회하는 것이 아닌, 실제 사용하는 시점에 조회할 수 있도록 한다.
프록시란
위임, 대리라는 의미로 JPA에선 데이터베이스를 통해서 조회한 실제 엔티티 객체말고 데이터베이스 조회를 미루는 역할을 수행하는 가짜 엔티티 객체를 의미한다.
- 실제 클래스를 상속해서 만들어지므로 실제 클래스와 겉모습이 유사
- 프록시 객체는 실제 객체의 참조(target)를 보관함.
- 따라서 프록시 객체를 호출하면 해당 참조를 가지고 실제 객체의 메소드 호출
entityManager.find(): 데이터베이스를 통해서 실제 엔티티 객체 조회
entityManager.getReference(): 데이터베이스 조회를 미루는 프록시 객체 조회
프록시 초기화 과정
아래와 같은 코드가 있다고 하자.
Member member = em.getReference(Member.class, "id1");
member.getName();
- getReference 호출을 통해 프록시 객체를 생성한다.
- 해당 프록시 객체가 영속성 컨텍스트에 존재하면 프록시 객체가 아닌 실제 객체를 반환한다. 여기선 존재하지 않으므로 실제 엔티티 객체의 참조가 아닌 프록시 객체를 얻는다.
- getName()을 호출한다.
- 메서드를 호출하면 영속성 컨텍스트에 초기화를 요청한다. 하지만 영속성 컨텍스트에는 실제 객체의 참조를 가지고 있지 않다.
- 따라서 DB를 조회하여 정보를 얻은 후 엔티티 객체를 생성한다.
- 프록시 객체 내부의 target은 실제 엔티티 객체 참조를 가지고 이를 통해 실제 객체의 메서드를 호출한다.
프록시의 특징
- 프록시 객체는 처음 사용할 때 한 번만 초기화된다.
- 프록시 객체를 초기화 한다는 것은 프록시 객체가 실제 엔티티로 바뀌는 것은 아니라 프록시 객체를 통해서 실제 엔티티에 접근이 가능해지는 것을 뜻한다.
- 위에서 프록시 객체는 원본 엔티티를 상속받으므로 타입 체크시 ==이 아닌 instance of 를 사용해야 한다.
- 영속성 컨텍스트에 찾는 엔티티가 이미 있다면 프록시 객체를 사용할 필요가 없으므로 em.getReference()를 호출해도 실제 엔티티를 반환한다.
- 마찬가지로 프록시 객체가 영속성 컨텍스트에 존재한다면 find() 사용 시에 프록시 객체가 반환되고 내부에서 프록시를 초기화한다.
- JPA는 같은 트랜잭션에서 영속성 컨텍스트의 동일성을 보장해야하므로 (PK가 같다면 동일한 인스턴스 반환)
- 준영속 상태의 프록시의 경우 영속성 컨텍스트의 도움을 받을 수 없으므로 초기화하면 문제가 발생한다.
지연 로딩, 즉시 로딩
프록시를 사용하면 연관된 객체의 데이터를 실제 사용하는 시점에 조회할 수 있도록 한다고 하였다.
이를 지연 로딩이라고 하고, 반대로 연관된 객체의 정보를 바로 가져오는 것을 즉시 로딩이라고 한다.
- 즉시 로딩 전략
- XxxToOne, XxxToMany의 FetchType을 EAGER로 설정
- 이렇게 하면 Member 조회 시 항상 Team도 조회가 가능하다.
- 가능하면 조인을 사용해서 SQL 한 번에 Member, Team을 함께 조회한다.
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "TEAM_ID")
private Team team;
- 지연 로딩 전략
- XxxToOne, XxxToMany의 FetchType을 LAZY로 설정
- 이렇게 설정하면 Member를 조회할 때 연관된 객체인 team은 프록시 객체로 가지고 있으며 team 필드를 실제로 사용하는 시점에 프록시 초기화 과정이 일어난다.
- DB에 team 조회 쿼리가 발생
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
즉시 로딩 전략의 경우 JPQL에서 N+1 문제를 일으키며 이로 인해 예상치 못한 쿼리가 발생할 수 있다.
따라서 지연 로딩 전략을 사용하는 것을 권장한다.
참고로 @ManyToOne, @OneToOne은 기본이 즉시 로딩이므로 위처럼 설정을 LAZY로 바꿔주어야 하고,
@OneToMany, @ManyToMany는 기본이 지연 로딩이다.
만약 연관된 데이터를 자주 사용해야 하는 상황이라면 그래도 즉시 로딩을 사용하지 말고 지연 로딩 전략에서 페치 조인이나, 엔티티 그래프 기능을 사용하는 것을 권장한다.
참고
자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 | 김영한 - 인프런
김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도
www.inflearn.com
'JPA' 카테고리의 다른 글
[JPA] 데이터 타입 (0) | 2024.12.29 |
---|---|
[JPA] 영속성 전이, 고아 객체 (0) | 2024.12.29 |
[JPA] N + 1 문제 (0) | 2024.12.29 |
[JPA] 상속 관계 매핑, @MappedSuperclass (0) | 2024.12.29 |
[JPA] 연관관계 매핑 (0) | 2024.12.29 |