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의 DTO 프로젝션 (DTO Projection) 본문

카테고리 없음

QueryDSL의 DTO 프로젝션 (DTO Projection)

che01 2025. 6. 27. 17:53

DTO 프로젝션이란?

데이터베이스에서 필요한 필드만 선택해서 조회하고, 그 결과를 DTO 객체에 바로 매핑하여 반환하는 방법이다. 엔티티 전체를 조회하는 대신 원하는 데이터만 가져와 성능을 최적화할 수 있다.

사용하는 이유

성능 최적화

  • 불필요한 필드를 제외하고 필요한 데이터만 조회
  • 네트워크 트래픽 감소
  • 메모리 사용량 절약

응답 최적화

  • API 응답에 필요한 정보만 포함
  • 클라이언트 처리 속도 개선
  • 민감한 정보 노출 방지

코드 가독성

  • 복잡한 조인 결과를 명확한 DTO로 표현
  • 비즈니스 로직과 데이터 구조 분리

QueryDSL에서 DTO 프로젝션 방법

1. Projections.fields() - 필드 이름 기반 매핑

List<TodoSearchResponse> results = queryFactory
    .select(Projections.fields(
        TodoSearchResponse.class,
        todo.title.as("title"),
        todo.createdAt.as("createdAt"),
        manager.countDistinct().as("managerCount")
    ))
    .from(todo)
    .leftJoin(todo.managers, manager)
    .fetch();

2. Projections.constructor() - 생성자 기반 매핑

List<TodoSearchResponse> results = queryFactory
    .select(Projections.constructor(
        TodoSearchResponse.class,
        todo.title,
        todo.createdAt,
        manager.countDistinct()
    ))
    .from(todo)
    .leftJoin(todo.managers, manager)
    .fetch();

3. Projections.bean() - Setter 기반 매핑

List<TodoSearchResponse> results = queryFactory
    .select(Projections.bean(
        TodoSearchResponse.class,
        todo.title.as("title"),
        todo.createdAt.as("createdAt"),
        manager.countDistinct().as("managerCount")
    ))
    .from(todo)
    .leftJoin(todo.managers, manager)
    .fetch();

실제 사용 예시

DTO 클래스 정의

public class TodoSearchResponse {
    private String title;
    private LocalDateTime createdAt;
    private Long managerCount;
    private Long commentCount;
    
    // 생성자, getter, setter
}

복합 조회 쿼리

public List<TodoSearchResponse> searchTodos(String keyword) {
    return queryFactory
        .select(Projections.fields(
            TodoSearchResponse.class,
            todo.title.as("title"),
            todo.createdAt.as("createdAt"),
            manager.countDistinct().as("managerCount"),
            comment.countDistinct().as("commentCount")
        ))
        .from(todo)
        .leftJoin(todo.managers, manager)
        .leftJoin(todo.comments, comment)
        .where(todo.title.contains(keyword))
        .groupBy(todo.id)
        .fetch();
}

주의사항

필드 매핑 규칙

  • fields(): DTO 필드명과 쿼리 결과 컬럼명이 일치해야 함
  • constructor(): 생성자 파라미터 타입과 순서가 일치해야 함
  • bean(): setter 메서드가 존재해야 함

별칭(alias) 사용

// 필드명이 다를 때 별칭 사용
todo.createdDate.as("createdAt")
manager.countDistinct().as("managerCount")

복잡한 표현식 처리

// 조건부 표현식도 가능
Expressions.cases()
    .when(todo.status.eq("COMPLETED")).then("완료")
    .otherwise("진행중")
    .as("statusText")

장점과 단점

장점

  • 필요한 데이터만 조회하여 성능 최적화
  • N+1 문제 회피 (연관 엔티티를 직접 사용하지 않음)
  • 명확한 데이터 구조 정의

단점

  • DTO 클래스 추가 정의 필요
  • 매핑 규칙을 정확히 지켜야 함
  • 복잡한 객체 그래프 표현에 한계

결론

DTO 프로젝션은 QueryDSL에서 성능과 가독성을 동시에 잡을 수 있는 효과적인 조회 방법이다. 특히 API 응답이나 복잡한 집계 쿼리에서 필요한 데이터만 정확히 가져올 때 매우 유용하다.