Backend/JPA
QueryDsl - 프로젝션을 DTO 결과로 반환하는 방법과 개념
개발자-제이
2025. 3. 17. 01:08
1. 프로젝션(Projection)이란?
프로젝션(Projection)은 쿼리 결과에서 특정 필드만 선택하여 반환하는 기법을 의미한다.
JPA에서 엔티티 전체를 조회하는 것이 아니라, 필요한 데이터만 선택하여 DTO로 변환하는 것이 대표적인 프로젝션 방식이다.
프로젝션을 사용하는 이유
- 불필요한 데이터 조회 방지 → 성능 최적화
- 비즈니스 요구 사항 반영 → 특정 데이터 구조로 변환하여 반환
- 데이터 보호 → 민감한 정보가 포함된 엔티티 전체 조회 방지
2. 순수 JPA에서 DTO 조회
DTO 클래스 정의
@Data
public class MemberDto {
private String username;
private int age;
public MemberDto() {}
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
순수 JPA DTO 조회 (new 키워드 사용)
List<MemberDto> result = em.createQuery(
"select new com.example.dto.MemberDto(m.username, m.age) " +
"from Member m", MemberDto.class)
.getResultList();
- 패키지명을 직접 입력해야 하므로 코드가 길어지고 가독성이 떨어짐
- 생성자 방식만 지원하여 필드가 많아질 경우 불편함
3. QueryDSL에서 DTO 조회
QueryDSL에서는 DTO 조회를 더 편리하게 하기 위해 3가지 방식을 제공한다.
3.1 프로퍼티 접근 (Setter 방식)
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
- 장점: Setter를 활용하여 DTO 값을 설정하므로 유지보수에 유리
- 단점: Setter가 필요한 경우 불변 객체패턴을 적용하기 어려움
3.2 필드 직접 접근 (Field 방식)
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
- 장점: Setter 없이 필드에 직접 주입 가능
- 단점: 캡슐화 위반 가능성이 있음
3.3 생성자 방식 (Constructor 방식)
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
- 장점: 불변 객체(Immutable Object) 사용 가능
- 단점: 생성자의 매개변수 개수가 많아질 경우 가독성이 떨어짐
4. DTO 필드명이 다를 경우 처리
DTO 필드명이 엔티티 필드명과 다를 경우, 매핑이 되지 않는 문제가 발생할 수 있다.
해결 방법 (별칭 활용)
List<UserDto> fetch = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"), // username → name 매핑
ExpressionUtils.as(
JPAExpressions
.select(memberSub.age.max())
.from(memberSub), "age") // 서브쿼리 결과를 age에 매핑
))
.from(member)
.fetch();
- 필드명이 다를 경우 .as("새 필드명")을 활용하여 매핑 가능
5. @QueryProjection을 활용한 DTO 조회
@QueryProjection을 활용하면 컴파일 타임에서 타입 체크가 가능하여 안전성을 확보할 수 있다.
DTO에 @QueryProjection 적용
@Data
public class MemberDto {
private String username;
private int age;
public MemberDto() {}
@QueryProjection
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
QueryDSL에서 @QueryProjection 활용
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
- 장점: 타입 안정성을 확보할 수 있음
- 단점: QueryDSL 어노테이션 의존성이 증가하고 Q 파일을 생성해야 함
6. 비교 정리
방식 | 특징 | 장점 | 단점 |
순수 JPA new 키워드 | 생성자 기반 DTO 매핑 | 간단한 방식 | 패키지 경로 입력 필요 |
QueryDSL Projections.bean() | Setter 기반 매핑 | 유지보수 용이 | 불변 객체 사용 어려움 |
QueryDSL Projections.fields() | 필드 직접 할당 | Setter 없이 활용 가능 | 캡슐화 위반 가능성 |
QueryDSL Projections.constructor() | 생성자 기반 매핑 | 불변 객체 사용 가능 | 생성자 매개변수 많아질 경우 가독성 저하 |
@QueryProjection | 컴파일 타임 타입 체크 | 타입 안정성 보장 | Q 파일 생성 필요 |
반응형