개요
프로젝트를 진행하던 중, QueryDSL을 사용해 동적 쿼리를 작성하면서 DTO 매핑 과정에서 필드 순서가 잘못되어 한동안 디버깅에 어려움을 겪었습니다.
본 글에서는 이러한 실수를 방지할 수 있는 @QueryProjection 어노테이션에 대해 학습한 내용을 정리하고자 합니다.
@QueryProjction어노테이션이란❓
@QueryProjection 어노테이션은 QueryDSL을 사용한 동적 쿼리 작성 시 DTO에 타입 안전한 방식으로 값을 매핑할 수 있도록 도와주는 기능입니다.
QueryDSL은 Java에서 SQL과 같은 쿼리를 타입 안전하게 작성할 수 있게 해주는 라이브러리로, 주로 Spring Data JPA와 함께 사용되며 복잡한 동적 쿼리를 작성할 때 유용합니다.
@QueryProjction 사용 방법
@QueryProjection을 사용하려면 QueryDSL의 코드 생성 기능을 이용해야 합니다. 이를 통해 컴파일 타임에 타입 안전한 쿼리를 작성할 수 있는 DTO 클래스의 QType 파일이 생성됩니다.
- DTO 클래스에 @QueryProjection 어노테이션 추가
- DTO 클래스에 @QueryProjection을 추가하면, QueryDSL이 해당 클래스를 기반으로 타입 안전한 QClass를 생성합니다.
- QueryDSL Maven/Gradle 설정
- QueryDSL의 코드 생성 기능을 사용하기 위해 Gradle이나 Maven에 QueryDSL 관련 플러그인을 설정해야 합니다.
- 타입 안전한 DTO 사용
- 생성된 QClass를 통해 QueryDSL 쿼리에서 타입 안전하게 DTO를 사용할 수 있습니다.
1. @QueryProjection 어노테이션 추가
import com.querydsl.core.annotations.QueryProjection;
import lombok.Getter;
@Getter
public class PostResponseDto {
private Long postId;
private String title;
private String content;
@QueryProjection
public PostResponseDto(Long postId, String title, String content) {
this.postId = postId;
this.title = title;
this.content = content;
}
}
위 코드는 PostResponseDto 클래스에 @QueryProjection을 추가하여, QueryDSL이 해당 클래스를 기반으로 QClass를 생성할 수 있도록 합니다.
2. 의존성 추가
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
3. 사용
QPost post = QPost.post;
List<PostResponseDto> posts = queryFactory
.select(new QPostResponseDto(post.id, post.title, post.content))
.from(post)
.where(post.title.contains("Spring"))
.fetch();
위 예시는 QueryDSL을 사용하여 PostResponseDto 객체로 결과를 조회하는 쿼리입니다.
QPostResponseDto는 @QueryProjection으로 생성된 QClass입니다. 이 방식을 사용하면 컴파일 타임에 필드 타입과 순서를 체크할 수 있어 타입 안전한 쿼리를 작성할 수 있습니다.
@QueryProjction이 필요한 이유
JPA를 사용하여 데이터를 조회할 때, 엔티티를 그대로 반환하는 것이 아니라 DTO를 사용하여 필요한 데이터만 추출하거나 가공하는 경우가 많습니다.
특히 조회 성능 최적화를 위해 필요한 필드만 조회할 때, JPQL이나 QueryDSL을 사용해 DTO로 값을 매핑합니다.
일반적으로 QueryDSL로 DTO를 매핑할 때는 생성자를 이용하거나 setter 메서드를 이용합니다.
생성자를 이용한 DTO매핑
List<MultimediaDto.Response> content = queryFactory
.select(Projections.constructor(MultimediaDto.Response.class,
multimedia.multiMediaId,
multimedia.fileName,
multimedia.fileType,
multimedia.fileSize,
multimedia.post.postId,
multimedia.createdAt
))
.from(multimedia)
.fetch();
Projections.constructor를 사용해 DTO의 생성자를 통해 값을 매핑합니다.
setter를 이용한 DTO매핑
List<MultimediaDto.Response> content = queryFactory
.select(Projections.bean(MultimediaDto.Response.class,
multimedia.multiMediaId,
multimedia.fileName,
multimedia.fileType,
multimedia.fileSize,
multimedia.post.postId,
multimedia.createdAt
))
.from(multimedia)
.fetch();
Projections.bean을 사용하여 setter 메서드를 통해 DTO에 값을 매핑합니다.
그러나 생성자, setter 방식은 매핑 과정에서 필드의 타입이 맞지 않거나 순서가 잘못되었을 때 컴파일 타임이 아닌 런타임에 오류가 발생할 수 있습니다.
이런 문제를 해결하기 위해 @QueryProjection을 사용하면, 컴파일 타임에 타입 체크를 할 수 있어 안정성을 높일 수 있습니다.
@QueryProjection
List<MultimediaDto.Response> content = queryFactory
.select(new QMultimediaDto_Response(
multimedia.multiMediaId,
multimedia.fileName,
multimedia.fileType,
multimedia.fileSize,
multimedia.post.postId,
multimedia.createdAt
))
QMultimediaDto_Response는 @QueryProjection 어노테이션을 사용해 컴파일 시 자동으로 생성된 QClass로, 타입 안전한 방식으로 DTO 필드에 값을 매핑하는 데 사용됩니다.
@QueryProjction의 장단점
장점
타입 안정성
- 컴파일 타임에 필드 타입과 순서를 체크할 수 있어, 런타임 오류를 줄이고 더 안전한 코드를 작성할 수 있습니다.
가독성
- DTO에 생성자를 통해 직접 매핑하는 방식보다 가독성이 높아지고, 유지보수가 용이해집니다.
코드 자동 생성
- QueryDSL의 코드 생성 기능을 통해 자동으로 QClass를 생성하므로, 별도의
추가적인 코드를 작성할 필요가 없습니다.
단점
DTO에 대한 의존성
- DTO에 @QueryProjection을 사용하면 QueryDSL에 의존성이 생깁니다.
- DTO는 본래 의존성이 적은 상태로 설계하는 것이 좋기 때문에, 이 의존성이 부담이 될 수 있습니다.
빌드 속도 저하
- QueryDSL의 코드 생성 과정에서 빌드 시간이 조금 더 걸릴 수 있습니다. 특히 대규모 프로젝트에서는 이 부분이 문제될 수 있습니다.
선택적 사용 필요
- 모든 DTO에 @QueryProjection을 사용하는 것보다는, 특정 상황(복잡한 쿼리나 타입 안전성이 중요한 부분)에 선택적으로 적용하는 것이 좋습니다.
'Framework > JPA' 카테고리의 다른 글
[JPA] @Modifying 어노테이션이란 무엇일까❓ (0) | 2024.10.01 |
---|---|
[JPA] 프록시(proxy)객체란 무엇일까❓ (1) | 2024.09.15 |
[JPA] Spring Data JPA의 페이징과 정렬을 쉽게 구현하는 방법: Pageable과 PageRequest (0) | 2024.09.02 |
[JPA] JPA에서 단방향 및 양방향 관계 이해 (@ManyToOne, @OneToMany, @OneToOne, @ManyToMany) (1) | 2024.08.23 |
[JPA] JPA에서 낙관적 락(Optimistic Locking)을 통한 동시성 제어하기 (@Version) (0) | 2024.08.22 |