Notice
Recent Posts
Recent Comments
Link
«   2025/08   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Tags
more
Archives
Today
Total
관리 메뉴

che01 님의 블로그

QueryDSL leftJoin에서 fetchJoin 없이도 N+1 문제가 발생하지 않는 경우 본문

카테고리 없음

QueryDSL leftJoin에서 fetchJoin 없이도 N+1 문제가 발생하지 않는 경우

che01 2025. 6. 27. 17:48

결론

QueryDSL에서 leftJoin()을 사용할 때 항상 fetchJoin()이 필요한 것은 아니다. DTO 프로젝션으로 조회하는 경우에는 N+1 문제가 발생하지 않는다.

상황별 N+1 문제 발생 여부

1. 엔티티 직접 조회 - N+1 발생함

List<Todo> todos = queryFactory
    .selectFrom(todo)
    .leftJoin(todo.managers, manager)  // fetchJoin 없음
    .fetch();

// 연관 객체 접근 시 N+1 발생
for (Todo t : todos) {
    t.getManagers().size();  // 각 Todo마다 추가 쿼리 실행
}

2. DTO 프로젝션 조회 - N+1 발생하지 않음

List<TodoSearchResponse> result = queryFactory
    .select(Projections.fields(
        TodoSearchResponse.class,
        todo.title.as("title"),
        manager.countDistinct().as("managerCount"),
        comment.countDistinct().as("commentCount")
    ))
    .from(todo)
    .leftJoin(todo.managers, manager)     // fetchJoin 없어도 됨
    .leftJoin(todo.comments, comment)     // fetchJoin 없어도 됨
    .fetch();

왜 DTO 프로젝션에서는 N+1이 발생하지 않는가?

  1. 엔티티를 영속성 컨텍스트에 올리지 않음: DTO는 단순한 데이터 전송 객체로, JPA가 관리하지 않는다.
  2. Lazy Loading이 작동하지 않음: 엔티티가 아니므로 연관 객체에 대한 프록시 생성이나 지연 로딩이 일어나지 않는다.
  3. 필요한 데이터만 한 번에 조회: SELECT 절에 명시된 필드들만 조인을 통해 한 번의 쿼리로 가져온다.

fetchJoin이 필요한 경우

엔티티 조회 시 연관 객체도 함께 사용하는 경우

List<Todo> todos = queryFactory
    .selectFrom(todo)
    .leftJoin(todo.managers, manager).fetchJoin()  // fetchJoin 필요
    .fetch();

// 이제 N+1 없이 연관 객체 사용 가능
for (Todo t : todos) {
    t.getManagers().size();  // 추가 쿼리 없음
}

DTO에서 연관 객체의 내부 필드에 접근하는 경우

.select(Projections.fields(
    TodoSearchResponse.class,
    todo.title.as("title"),
    manager.user.nickname.as("managerNickname")  // 연관 객체의 내부 필드
))
.from(todo)
.leftJoin(todo.managers, manager).fetchJoin()  // fetchJoin 필요
.leftJoin(manager.user, user).fetchJoin()      // 추가 fetchJoin 필요

정리

  • DTO 프로젝션 + 집계 함수: fetchJoin 불필요
  • 엔티티 조회 + 연관 객체 사용: fetchJoin 필수
  • DTO 프로젝션 + 연관 객체 내부 필드: fetchJoin 필요

성능 최적화를 위해서는 용도에 맞는 조회 방식을 선택하는 것이 중요하다.