프록시 (Proxy)
프록시는 엔티티 객체를 흉내 낸 가짜 객체입니다. 프록시 덕분에 실제 엔티티를 조회하는 시점을 미룰 수 있습니다.그렇다면, 실제 엔티티를 조회하는 시점을 미뤄야 할 상황은 언제일까요? Me
praaay.tistory.com
프록시를 정리한 글의 연장선으로 즉시 로딩과 지연 로딩에 대해 이야기해 보겠습니다.
위 그림처럼 Member 엔티티와 Team 엔티티가 연관관계로 묶여있을 때, Member 엔티티를 조회하면 연관관계로 묶인 Team 엔티티까지 조회해야 할까요?
두 엔티티를 모두 필요로 하는 상황이라면 좋겠지만, 위 그림처럼 Member 엔티티만 필요로 한다면 연관관계로 묶였다고 하더라도 두 엔티티를 함께 조회할 필요는 없습니다. Member 엔티티를 조회하면 Member 엔티티만 조회되면 됩니다.
지연 로딩(LAZY)을 사용해서 Member 엔티티를 조회할 때 연관관계로 묶인 Team 엔티티에는 프록시 객체를 할당하고 Member 엔티티만 데이터베이스에서 조회하도록 만들 수 있습니다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne(fetch = FetchType.LAZY) //**
@JoinColumn(name = "TEAM_ID")
private Team team;
..
}
Member 엔티티 클래스의 team 필드를 @ManyToOne(fetch = FetchType.LAZY) 애노테이션을 통해 지연 로딩으로 설정하면 Member 엔티티를 조회할 때 team 필드를 프록시 객체로 조회합니다.
다시 말해 Member 엔티티를 조회할 때 Team 엔티티를 함께 조회하지 않고 프록시 객체를 할당합니다. 이후에 team의 속성을 사용하는 시점에 team에 할당되어 있던 Team을 상속받은 프록시가 초기화되면서 프록시의 targat으로 실제 Team 객체가 붙습니다.
위 그림과 같이 지연 로딩으로 설정된 필드는 데이터베이스에서 실제 엔티티를 조회하지 않고 프록시 엔티티가 들어갑니다. 그러고 프록시 객체의 속성이 사용되는 시점에 프록시 초기화가 일어납니다.
member.getTeam() 했을 때는 실제 Team 엔티티를 조회하는 쿼리가 나가지 않습니다.
이때는 team의 속성을 사용하는 것이 아니라 team 필드 자체를 조회할 뿐이기 때문입니다. team.getName() 호출할 때 실제 Team 엔티티를 조회하는 쿼리를 내보내며 프록시 객체가 초기화됩니다. 프록시 객체의 초기화 과정의 위에서 첨부한 글에서 자세히 살펴봅시다.
지연 로딩과 반대로 즉시 로딩(EAGER)은 연관관계로 묶인 모든 엔티티를 데이터베이스에 조회하여 반환합니다.
따라서 프록시를 활용하지 않고 실제 엔티티만을 가지고 동작합니다. 즉시 로딩의 경우 JPA 구현체는 가능하면 조인을 사용해서 SQL 한 번에 연관관계로 묶인 엔티티들을 조회합니다.
결론
결론적으로 실무에서는 지연 로딩(LAZY)으로 모든 연관관계를 설정하고 연관관계가 묶인 엔티티들을 한 번에 가져와야 하는 상황에서는 JPQL 패치 조인 방법을 사용해야 합니다. 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생할 수 있습니다. 또한 즉시 로딩은 JPQL에서 N+1 문제를 일으킵니다.
List<Member> members = em.createQuery("select m from Member m", Member.class)
.getResultList();
위와 같이 직접 JPQL을 작성하여 엔티티를 조회할 경우 데이터베이스에 쿼리를 직접 날리게 됩니다. em.find()의 경우 영속성 컨텍스트가 최적화를 돕기 때문에 불필요한 쿼리를 내보내지 않지만, JPQL은 데이터베이스에 쿼리를 직접 날리기 때문에 데이터베이스에 Member 타입들을 조회하고 그 Member 타입에 Team 필드가 즉시로딩이라면 Team 필드까지 일일이 조회하게 되며 방대한 쿼리가 나가게 되는 N+1 문제가 발생합니다.
이를 해결하기 위해서 먼저 모든 연관관계를 지연 로딩(LAZY)으로 설정하고 JPQL에서 패치 조인을 사용하여 런타임에 동적으로 필요한 것들을 선택해서 한 번에 가져올 수 있습니다. JPQL의 패치 조인에 대해서는 나중에 더 자세히 살펴봅시다.
// JPQL의 패치 조인 예시
"select m from Member m join fetch m.team"
참고로 @OneToMany와 @ManyToMany는 기본이 지연 로딩이지만,
@ManyToOne과 @OneToOne은 기본이 즉시 로딩이기 때문에 별도로 지연 로딩으로 설정해야 합니다.
'Spring > JPA' 카테고리의 다른 글
orphanRemoval (고아 객체) (0) | 2024.10.23 |
---|---|
CASCADE (영속성 전이) (2) | 2024.10.21 |
프록시 (Proxy) (0) | 2024.10.21 |
@MappedSuperclass (0) | 2024.10.17 |
상속관계 매핑 (2) | 2024.10.17 |