@EntityGraph와 Fetch Join은 모두 N+1 문제를 해결하기 위해 사용하는 도구입니다.
JPA에서 연관된 엔티티를 즉시 로딩(Fetch Join) 하도록 선언하는 방법입니다.
내부적으로 SQL문의 JOIN FETCH와 유사한 SQL을 자동 생성해줍니다.
JPQL 없이도 선언적으로 Fetch Join 사용이 가능합니다.
JOIN FETCH를 작성하여 사용합니다.@Query("SELECT p FROM Post p JOIN FETCH p.author")
List<Post> findAllWithAuthor();
즉 위의 코드는 Post를 전체 조회 할 때 author필드도 함께 조회한다는 뜻을 지닙니다.
JPA Repository 인터페이스의 메서드에서 @EntityGraph(attributePaths = {연관필드명})으로 사용합니다.@EntityGraph(attributePaths = {"user"})
List<Post> findAll();
즉 위의 코드는 Post를 전체 조회 할 때 user필드도 함께 조회한다는 뜻을 지닙니다.
그렇다면 두 기능 모두 N + 1문제를 해결하기 위해 사용하는 기능인데, @EntityGraph는 왜 사용해야 할까요?
저는 처음 @EntityGraph을 알게 되었을 때, 굳이 사용해야하나? 그냥 쿼리문 안에 JOIN FETCH쓰면 되는 거 아닌가? 하는 생각이 들었습니다.
그래서 어떤 상황에서 @EntityGraph를 사용하는지, 둘의 차이점에 대해 알아보았습니다.
| 항목 | @EntityGraph | Fetch Join (JOIN FETCH) |
|---|---|---|
| 사용 위치 | Spring Data JPA Repository 메서드 | JPQL 쿼리 (@Query 또는 EntityManager 사용 시) |
| 문법 형태 | @EntityGraph(attributePaths = {"user"}) | SELECT p FROM Post p JOIN FETCH p.user |
| 쿼리 작성 필요 여부 | ❌ 쿼리 없이 선언적으로 지정 | ✅ 직접 JPQL 쿼리 작성 필요 |
| 가독성 및 유지보수 | ✅ 깔끔하고 선언적 | ❌ 복잡한 쿼리 구조 가능성 있음 |
| 중첩 fetch 경로 지원 | ✅ "user.company"처럼 중첩 필드 fetch 가능 | ✅ JOIN을 여러 번 명시해 중첩 fetch 가능 |
| 조건부 조인 가능 여부 | ❌ 불가능 (조건 지정 불가) | ✅ WHERE, ON 조건 자유롭게 사용 가능 |
| 페이징(Pageable) 호환 | ✅ 단일 관계(@ManyToOne 등)에서는 안전하게 사용 가능 | ⚠️ 컬렉션 조인 시 페이징 깨짐 |
| 쿼리 재사용성 | ✅ NamedEntityGraph로 재사용 가능 | ❌ 반복 쿼리 직접 작성해야 함 |
| N+1 문제 해결 | ✅ 가능 | ✅ 가능 |
| JPA 표준 지원 여부 | ✅ (jakarta.persistence.EntityGraph) | ✅ (JOIN FETCH는 JPQL 표준 문법) |
| 대표 용도 | 단순한 fetch join, 반복되는 fetch 로직 선언 | 복잡한 조건 포함, 고도화된 쿼리 필요 시 |
| 항목 | @EntityGraph | Fetch Join (JOIN FETCH) |
|---|---|---|
| 쿼리 생성 주체 | Spring Data JPA가 메서드 분석 후 내부적으로 JPQL + JOIN FETCH 생성 | 개발자가 작성한 JPQL 그대로 사용됨 |
| 기본 조인 전략 | 항상 LEFT JOIN FETCH (null 값 포함 가능성 고려) | 기본은 INNER JOIN FETCH, 명시적으로 LEFT 변경 가능 |
| 쿼리 실행 시점 | Repository 메서드 호출 시, 동적으로 쿼리 생성 및 실행 | JPQL이 컴파일 시점에 고정됨 |
| JPQL에서 select 절 조작 | 불가 – 항상 루트 엔티티 기준으로 select | 가능 – 필요한 필드만 SELECT new DTO(...)로 지정 가능 |
| 쿼리 결과 제어 가능성 | 제한적 – fetch 대상만 제어 가능 | 높음 – select, where, order by, join on 등 자유롭게 지정 가능 |
| 쿼리 해석의 유연성 | 자동 처리 – 단순 fetch join 목적에 최적화 | 수동 처리 – 복잡한 로직 및 성능 튜닝 시 유리 |
| 결과 중복 제거 처리 | 기본적으로 필요 없음 (단일 연관일 때) | 컬렉션 fetch 시 중복 가능 → DISTINCT 필요 |
| 페치 대상 지정 방식 | attributePaths 문자열로 경로 지정 | JOIN FETCH로 명시적 경로 지정 |
| join fetch 최적화 전략 | JPA 구현체가 내부적으로 최적화 적용 가능 | 작성한 쿼리에 따라 성능이 크게 달라짐 |
둘의 차이점을 알아보니 어떤 상황에서 @EntityGraph를 사용하는지에 대한 판단이 비교적 쉬워졌습니다.
| 번호 | 사용해야 하는 상황 | 설명 |
|---|---|---|
| 1 | JPQL 없이 간단하게 fetch join 하고 싶을 때 | 쿼리 작성 없이 메서드 위에 @EntityGraph만 선언하면 자동으로 fetch join 처리 가능 |
| 2 | 중첩된 연관 엔티티를 함께 로딩하고 싶을 때 | "user.company.department"처럼 깊은 연관 관계도 선언만으로 fetch 가능 |
| 3 | 반복되는 fetch join을 재사용하고 싶을 때 | @NamedEntityGraph로 선언하면 여러 메서드에서 일관되게 재사용 가능 |
| 4 | 쿼리 조건, DTO 매핑 없이 전체 엔티티 조회만 필요한 경우 | 복잡한 select 조건 없이 루트 엔티티와 연관 필드만 간단히 조회할 때 적합 |