반응형
Querydsl을 통해 쿼리문을 작성하면서 프로젝션 하는 방법에 대해 정리하고자 합니다.
그 중 Type Safe 체크와 immutable 객체 선언이 가능한 생성자 + @QueryProjection을 사용하고 있습니다.
Projection이란
데이터베이스에서 프로젝션은 주어진 릴레이션에 대해 각 레코드에서 원하는 애트리뷰트 집합으로만 이루어진 새로운 릴레이션을 만드는 단일 연산자입니다.
Querydsl에서 Projection 다루기
프로젝션 대상이 1개
List<String> result = queryFactory
.select(member.username)
.from(member)
.fetch();
- 프로젝션 대상이 하나인 경우 타입을 명확하게 지정할 수 있습니다.
- 프로젝션 대상이 2개 이상이면 Tuple이나 DTO로 조회합니다.
프로젝션 대상이 2개 이상
1. Tuple 조회
// 조회하는 컬럼이 2개 이상이면 Tuple을 반환
List<Tuple> result = queryFactory
.select(member.username, member.age)
.from(member)
.fetch();
//*** ***//
for (Tuple tuple : result) {
String username = tuple.get(member.username);
Integer age = tuple.get(member.age);
System.out.println("username=" + username);
System.out.println("age=" + age);
}
- tupe.get(member.username)처럼 get() 메서드와 QEntity.Column으로 데이터 컬럼을 조회합니다.
2. DTO 조회
2.1 Querydsl Bean 생성
결과를 DTO로 반환할 때 접근 방법 3가지
- Property 접근
- Field 직접 접근
- Constructor 사용
Property 접근 - Setter 사용
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class,
member.username, member.age))
.from(member)
.fetch();
- Projections.bean 방식은 setter 기반으로 동작합니다.
- 이 때문에 사용하는 Dto에 setter를 추가로 열어주어야 하는 문제가 있음
- Response, Request 객체는 불변 객체로 설계하는 것이 바람직한 패턴이라 생각하여
해당 방법은 사용하지 않았습니다.
Field 직접 접근
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.Class,
member.username, member.age))
.from(member)
.fetch();
- Property 접근 방식과 유사하지만, getter/setter와 상관없이 그냥 필드에 데이터를 주입해줍니다.
- 필드 접근 방식은 DTO의 필드 이름과 query에 작성된 필드의 이름이 동일해야 합니다. 다를 경우 alias를 통해 맞춰주어야 합니다. 이러한 이유로 타입 체크를 따로 해주지 않아 사용하지 않았습니다.
별칭이 다를 때 (DTO - Datebase Table)
List<UserDto> fetch = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
ExpressionUtils.as(
JPAExpressions
.select(memberSub.age.max())
.from(memberSub), "age")))
.from(member)
.fetch();
- 별칭이 다를 때, as() 나 ExpressionUtils.as()를 사용하여 대체합니다.
Constructor 사용
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username, member.age))
.from(member)
.fetch();
- Projections.constructor를 사용하면 생성자 기반으로 바인딩합니다.
- 그렇기에 MemberDto 객체를 불변으로 가져갈 수 있습니다.
- 하지만, 값을 매핑할 때 생성자와 순서를 일치시켜야 바인딩 작업에 문제가 없습니다. 따로 컴파일 시점에서 체크할 수 없어 다른 방법을 사용합니다.
🔥 생성자 + @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;
}
}
- 생성자 함수에 @QueryProjection 어노테이션을 추가합니다.
- ./gradlew compileQuerydsl을 실행합니다.
- QMemberDto 가 생성되었는지 확인합니다.
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
- 컴파일러로 타입을 체크할 수 있어 가장 안전한 방법입니다.
- 불변 객체 선언, 생성자 그래도 사용할 수 있습니다.
단점으로는 DTO에 Querydsl 어노테이션을 유지해야 하며, DTO까지 Q 클래스를 만드러야 하는 점입니다.
하지만 개발자는 생성자에 어노테이션만 추가해 주면 되기에 큰 추가 작업이 없어, 타입 체크가 가능한 해당 방식을 주로 사용합니다.
반응형
'Spring Framework > QueryDSL' 카테고리의 다른 글
Querydsl 날짜 연산 문제 해결 : Interval 예약어 미지원 - Java 날짜 객체를 사용하기 (1) | 2024.11.15 |
---|---|
Querydsl OrderSpecifier를 활용한 동적 정렬 방법 - Pathbuilder, Sort (0) | 2024.10.18 |
[Querydsl] JPAExpressions를 활용한 Querydsl 서브쿼리 작성 방법 (0) | 2024.10.17 |
[QueryDsl] QueryDsl groupBy 여러 개 적용하기 (0) | 2024.08.10 |
[QueryDsl] QueryDsl 페이징, 검색, 필터링 쿼리 구현 - 페이징 최적화, BooleanExpression (2) | 2024.07.30 |