일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 1차원 DP
- 2차원 dp
- 99클럽
- @BeforeAll
- @BeforeEach
- @Builder
- @Entity
- @GeneratedValue
- @GenericGenerator
- @NoargsConstructor
- @Query
- @Table
- @Transactional
- Actions
- Amazon EFS
- amazon fsx
- Android Studio
- ANSI SQL
- ApplicationEvent
- assertThat
- async/await
- AVG
- AWS
- Azure
- bind
- builder
- button
- c++
- c++ builder
- c03
- Today
- Total
기록
QueryDSL에서 DTO 반환 방식: Projections.constructor vs. new QUserResponse 본문
QueryDSL에서 DTO 반환 방식: Projections.constructor vs. new QUserResponse
youngyin 2025. 1. 26. 00:00시작하면서
QueryDSL을 사용하여 데이터를 조회할 때 DTO를 반환하는 방법에는 크게 두 가지가 있습니다: Projections.constructor를 사용하는 방법과 **new QUserResponse(...)**를 사용하는 방법입니다. 이 글은 새로운 도메인에 대한 쿼리를 작성하려는 과정에서 작성되었습니다. 기존 도메인들을 살펴보니 두 방식이 혼재되어 있어, 어떤 방법을 선택해 DTO를 매핑하는 것이 더 적합할지 고민이 생겼습니다.
1. Projections.constructor 사용
Projections.constructor는 QueryDSL이 자동으로 DTO의 생성자를 호출하여 데이터를 매핑합니다. 예를 들어, 사용자 데이터를 단순히 가져와 응답 객체로 매핑하는 경우, 이 방식은 코드가 간결하고 유지보수가 쉬워 적합합니다. 특히, 복잡한 데이터 가공 없이 쿼리 결과를 바로 매핑해야 할 때 유용합니다. 이 방식은 간단한 매핑 로직을 구현할 때 주로 사용됩니다.
코드 예시:
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
private final JPAQueryFactory queryFactory;
public UserRepositoryCustomImpl(JPAQueryFactory queryFactory) {
this.queryFactory = queryFactory;
}
public List<QUserResponse> findUserResponses() {
QUser user = QUser.user;
return queryFactory
.select(Projections.constructor(UserResponse.class,
user.userId,
user.userName,
user.userEmail,
user.userRole,
user.userStatus
))
.from(user)
.fetch();
}
}
장점:
- 자동 매핑: QueryDSL이 생성자를 호출하여 결과를 자동으로 매핑합니다. 예를 들어, 사용자의 userId, userName 등의 필드가 UserResponse 생성자에 자동으로 매핑됩니다. 이렇게 단순한 필드 매핑은 코드가 간결해지고 빠르게 구현할 수 있습니다.
- 자동 매핑: QueryDSL이 생성자를 호출하여 결과를 자동으로 매핑합니다.
- 타입 안전성: QueryDSL이 컴파일 타임에 타입을 체크합니다.
- 간결성: 코드가 간결하고 유지보수가 쉽습니다.
단점:
- 복잡한 매핑이 필요한 경우 유연성이 부족할 수 있습니다.
- 생성자 외의 방식으로 객체를 생성해야 하는 경우에는 한계가 있습니다.
- 런타임 오류 가능성: Projections.constructor는 필드와 생성자 매개변수 타입이 정확히 일치해야만 동작하며, 그렇지 않은 경우 IllegalArgumentException 오류가 발생할 수 있습니다.
2. new QUserResponse(...) 사용
new QUserResponse(...) 방식은 QueryDSL에서 직접 생성자를 호출하여 DTO 객체를 반환하는 방법입니다. 예를 들어, User 객체에서 다양한 데이터를 조합하여 응답 객체를 생성해야 하는 경우, 이 방식이 적합합니다. 또한, 생성 과정에서 추가적인 데이터 가공이나 필드 연산이 필요할 때도 유용합니다. 이 방식을 사용하려면 DTO에 어노테이션(@QueryProjection)을 추가하여 QClass를 생성해야 합니다.
DTO 예제:
import com.querydsl.core.annotations.QueryProjection;
public class UserResponse {
private Long userId;
private String userName;
private String userEmail;
private String userRole;
private String userStatus;
@QueryProjection
public UserResponse(Long userId, String userName, String userEmail, String userRole, String userStatus) {
this.userId = userId;
this.userName = userName;
this.userEmail = userEmail;
this.userRole = userRole;
this.userStatus = userStatus;
}
// Getters and Setters
}
코드 예시:
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
public class UserRepositoryCustomImpl implements UserRepositoryCustom {
private final JPAQueryFactory queryFactory;
public UserRepositoryCustomImpl(JPAQueryFactory queryFactory) {
this.queryFactory = queryFactory;
}
public List<UserResponse> findUserResponses() {
QUser user = QUser.user;
return queryFactory
.select(new QUserResponse(
user.userId,
user.firstName.concat(" ").concat(user.lastName), // fullName 생성
user.userEmail,
user.userRole,
user.activityScore.multiply(2).add(5), // 복잡한 계산 적용
user.registrationDate.year().toString() // 연도 추출 및 변환
))
.from(user)
.fetch();
}
}
))
.from(user)
.fetch();
}
}
장점:
- 직접 제어: DTO 객체 생성 방식을 개발자가 명시적으로 제어할 수 있습니다.
- 복잡한 로직 지원: 추가 연산이나 복잡한 로직을 구현할 때 유리합니다.
- 다중 생성자 지원: DTO에 생성자가 여러 개 있는 경우, 특정 생성자를 명시적으로 선택해 사용할 수 있습니다.
단점:
- 타입 안전성 부족: 컴파일 타임에 타입 오류를 확인하기 어렵습니다.
- 코드 중복 가능성: 동일한 객체 생성 코드가 여러 곳에서 반복될 수 있습니다.
- 가독성 저하: 코드가 길어지고 복잡해질 가능성이 있습니다.
3. 두 방식의 차이점 요약
특징 Projections.constructor new QUserResponse(...)
자동 매핑 지원 | 지원됨 (QueryDSL이 자동 매핑) | 지원되지 않음 |
타입 안전성 | 컴파일 타임에 타입 체크 가능 | 런타임에 오류 가능성 |
복잡한 로직 지원 | 제한적 | 복잡한 로직에 적합 |
가독성 | 간결함 | 상대적으로 복잡함 |
유연성 | 생성자 기반으로 제한적 (예: 단순한 필드 매핑 시 적합) | 개발자가 자유롭게 로직 구성 가능 (예: 여러 필드를 조합하거나 계산하는 경우 적합) |
다중 생성자 지원 | 불명확 (매개변수 매칭 오류 가능) | 명시적으로 원하는 생성자를 선택 가능 |
4. 어떤 방식을 선택할까?
Projections.constructor는 다음과 같은 경우에 적합합니다:
- 간단한 DTO 매핑이 필요한 경우
- 코드의 간결성과 유지보수가 중요한 경우
- 타입 안정성을 중요하게 고려해야 하는 경우
**new QUserResponse(...)**는 다음과 같은 경우에 적합합니다:
- 복잡한 객체 생성 로직이 필요한 경우
- DTO 생성 과정에서 추가 연산이나 로직이 필요한 경우
- 특정 생성자 호출 방식을 명시적으로 지정해야 하는 경우
- 다중 생성자가 존재하고 원하는 생성자를 직접 선택해야 하는 경우
5. 결론
QueryDSL에서 DTO를 반환하는 방식은 프로젝트의 요구사항과 복잡성, 팀의 경험 수준, 그리고 유지보수 가능성에 따라 선택해야 합니다. 간단한 매핑 작업에는 Projections.constructor를, 복잡한 로직이 필요한 경우에는 **new QUserResponse(...)**를 사용하는 것이 더 적합합니다.
'Web > Spring' 카테고리의 다른 글
[JPA] @Query 주의점 : nativeQuery와 @Table(name="orders") (0) | 2025.01.25 |
---|---|
Vaadin 프레임워크 소개: hawkBit 사례 분석(1) (0) | 2025.01.24 |
JPA flush()와 clear(): JPA와 QueryDSL 사용 시 발생하는 1차 캐시 동기화 문제 해결하기 (0) | 2025.01.13 |
AWS Elasticache를 Spring Boot에 연결하기 (0) | 2025.01.06 |
JWT와 Redis를 활용한 사용자 인증 프로세스 설계 (0) | 2024.12.31 |