che01 님의 블로그
QueryDSL의 DTO 프로젝션 (DTO Projection) 본문
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 응답이나 복잡한 집계 쿼리에서 필요한 데이터만 정확히 가져올 때 매우 유용하다.