본문 바로가기
Spring 7기 프로젝트/코드 개선 + 테스트 코드 프로젝트

[트러블슈팅] 간단하게 N+1 문제를 해결하는 @EntityGraph

by JuNo_12 2025. 6. 5.

@EntityGraph란?

1. 기본 개념

@EntityGraph(attributePaths = {"user"})

의미: "Todo를 조회할 때 연관된 user도 함께 가져와줘!"


2. 왜 필요한가?

문제 상황:

// Todo 10개를 조회했다고 가정
List<Todo> todos = todoRepository.findAll();

// 각 Todo의 user 정보에 접근할 때마다 개별 쿼리 발생! (N+1 문제)
for (Todo todo : todos) {
    System.out.println(todo.getUser().getEmail()); // 매번 DB 쿼리!
}

 

쿼리 실행 결과:

-- 1번: Todo 조회
SELECT * FROM todos;

-- N번: 각 user 개별 조회 (N+1 문제!)
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
...

3. @EntityGraph 해결책

@EntityGraph(attributePaths = {"user"})
List<Todo> findAll();

 

쿼리 실행 결과:

-- 1번만 실행: JOIN으로 한 번에 가져옴!
SELECT t.*, u.*
FROM todos t 
LEFT JOIN users u ON t.user_id = u.id;

4. 간단한 비유

@EntityGraph 없이:

  • 📦 택배(Todo)를 받고
  • 📝 영수증(user)은 나중에 따로 요청해서 받기
  • → 택배마다 영수증 요청 = 비효율!

@EntityGraph 사용:

  • 📦 택배(Todo)와 📝 영수증(user)을 한 번에 받기
  • → 한 번의 요청으로 모든 것 해결 = 효율적!

5. 실제 사용 예시

// 기본 조회 (LAZY 로딩)
@ManyToOne(fetch = FetchType.LAZY)  // user는 나중에 조회
private User user;

// EntityGraph로 즉시 로딩
@EntityGraph(attributePaths = {"user"})  // user를 미리 같이 가져와!
Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);

6. 다른 방법들과 비교

방법 코드  장단점
JPQL @Query("SELECT t FROM Todo t JOIN FETCH t.user") 복잡하지만 세밀한 제어
@EntityGraph @EntityGraph(attributePaths = {"user"}) 간단하고 직관적 ✅
FetchType.EAGER @ManyToOne(fetch = EAGER) 항상 가져와서 비효율적 ❌

7. 더 복잡한 예시

// 여러 연관관계 함께 로딩
@EntityGraph(attributePaths = {"user", "managers"})
List<Todo> findByTitle(String title);

// 중첩된 연관관계까지 로딩
@EntityGraph(attributePaths = {"user", "managers.user"})
Optional<Todo> findWithManagersById(Long id);

 

핵심: @EntityGraph는 "이 데이터들을 한 번에 가져와줘!"라고 JPA에게 알려주는 간단한 방법!