반응형
JPA에서 멤버를 조회할 때 팀도 항상 조회해야 할까?
우리는 Member와 Team 간의 다대일(N:1) 관계를 가진 도메인을 가지고 있습니다.
그런데, 단순히 회원 정보만 필요한 비즈니스 로직에서 팀 정보까지 항상 같이 가져와야 할까?
JPA는 이 문제를 해결하기 위해 지연 로딩(LAZY Loading) 이라는 기능을 제공합니다.
@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 m = em.find(Member.class, 99L);
System.out.println("m.getTeam().getClass() = " + m.getTeam().getClass());
// 출력: class Team$HibernateProxy$xYz1234 (프록시 객체)
m.getTeam().getName(); // 이 시점에서 SELECT 쿼리 날아감
지연 로딩을 사용하면, team 필드는 프록시 객체로 채워지고, 실제로 getName() 등으로 접근할 때 그 시점에 DB에서 조회됩니다.
대부분 상황에 Member와 Team을 함께 사용한다면? (즉시 로딩 EAGER)
@ManyToOne(fetch = FetchType.EAGER) // 즉시 로딩
@JoinColumn(name = "TEAM_ID")
private Team team;
Member m = em.find(Member.class, 99L);
// 이 시점에 member와 team을 조인해서 **한 번의 쿼리**로 가져옴
System.out.println(m.getTeam().getClass());
// 출력: class Team (진짜 객체)
m.getTeam().getName(); // 이미 로딩되어 있음
실무에서는 즉시 로딩(EAGER)을 피하자 (LAZY만 사용하도록 권장)
이유 1. 원하지 않는 쿼리 발생
즉시 로딩은 JPA가 내부적으로 JOIN 쿼리를 자동으로 생성하기 때문에, 의도하지 않았던 SQL이 발생할 수 있습니다.
특히 모든 연관관계에 EAGER를 걸면… (너무많은 조인으로 성능이 나빠질 수 있음)
select * from member m
left join team t on m.team_id = t.id
left join ... // N개의 다른 엔티티들까지 조인됨
이유 2. JPQL 사용 시 N+1 문제 발생
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
- 이 JPQL은 Member만 조회하는 쿼리(SQL)로 번역됩니다.
- 그런데 Member의 team이 EAGER로 설정되어 있다면?
select * from member; -- 1회 (기본 쿼리)
select * from team where id=?; -- N회 (멤버 수만큼 반복 조회)
즉 1번의 최초쿼리 (멤버조회) + N번의 추가 쿼리가 발생하게 됩니다.
해결책 (항상 LAZY 로 사용)
1. fetch join으로 필요한 곳에서만 즉시 조회 (대부분 이방법으로 해결)
String jpql = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(jpql, Member.class).getResultList();
- 프록시 없이 실제 Team 객체까지 한 번에 로딩됩니다.
- N+1 문제 방지
- JPQL로 컨트롤 가능
2. 엔티티 그래프 사용하기
@EntityGraph(attributePaths = {"team"})
List<Member> findAll(); // Spring Data JPA 사용 시
- Repository 메서드에 애노테이션을 붙이면, 동적으로 fetch join 수행됨
더보기
실무에서 fetch join을 더 선호하는 이유
1. 쿼리 명시성
select m from Member m join fetch m.team
→ 어떤 테이블과 조인되고 있는지 직접 눈으로 확인 가능하고, 튜닝하기도 쉬움.
반면, @EntityGraph는 IDE에서 자동 완성도 부족하고, 쿼리가 어떤 식으로 실행되는지 추측해야 하는 경우가 많음.
2. 복잡한 조인에는 한계
select m from Member m
join fetch m.team
join fetch m.team.leader
위처럼 2단계 이상 중첩된 fetch join은 엔티티 그래프로는 거의 불가능하거나 매우 불편합니다.
3. Hibernate의 @BatchSize로 in-query 최적화
@BatchSize(size = 100)
@ManyToOne(fetch = FetchType.LAZY)
private Team team;
Lazy 로딩을 유지하면서도, N+1을 in 절 쿼리로 묶어서 최적화
select * from team where id in (?, ?, ?, ..., ?)
반응형
'프로그래밍 > JPA' 카테고리의 다른 글
[JPA] 값 타입 (1) | 2025.07.22 |
---|---|
[JPA] 영속성 전이 CASCADE (2) | 2025.07.17 |
[JPA] 프록시와 연관관계 관리 (0) | 2025.07.15 |
[JPA] Mapped Superclass - 매핑 정보 상속 (0) | 2025.07.03 |
[JPA] 상속관계 매핑 (2) | 2025.07.03 |