[QueryDsl] DTO로 결과 반환하기 (Projections)
QueryDsl을 활용하여 데이터를 조회할 때, @Entity로 매핑된 클래스와 다른 형태로 데이터를 조회해야 할 때가 종종 발생한다.
이때 활용하기 좋은 방법 몇가지를 소개하려고 한다.
기본 TUPLE 활용
우선 말해두고 싶은 점은 이 방식은 QueryDsl을 사용하는 레이어 외의 다른 영역에서도 사용할 예정이라면 의존성 관리 측면에서 추천하지 않는 방식이다.
Tuple이라는 클래스는 QueryDsl 이 제공하는 클래스이기 때문에 외부에서 사용하는 경우, 의존성에 영향을 미치게 되므로 내부에서 사용하는 경우에 활용하는 것이 좋아 보인다.
List<Tuple> categoryTupleList = jpaQueryFactory
.select(
qCategoriesJpo.categoryname,
qCategoriesJpo.description
)
.from(qCategoriesJpo)
.fetch();
for (Tuple row : categoryTupleList) {
// tuple 의 get 메서드를 활용하여 컬럼 값 추출
String categoryname = row.get(qCategoriesJpo.categoryname);
String description = row.get(qCategoriesJpo.description);
System.out.println("카테고리 : " + categoryname);
System.out.println("설명 : " + description);
}
아래의 세가지 방법은 QueryDsl에서 제공하는 Projections 클래스를 활용하는 방식이다.
이 방식들을 사용하면 쿼리 결과를 DTO 클래스 등에 바로 입력하여 사용할 수 있다.
프로퍼티 접근법
Projections 의 beans 메서드를 활용하는 방법으로, 지정한 클래스의 setter를 통해 컬럼 값을 넣는 방식이다.
기본생성자를 이용하여 클래스를 생성하고 setter를 통해 값을 넣는 것이 특징이다.
setter 를 활용하기 때문에 불변 객체엔 값을 제대로 할당할 수 없다.
List<SampleDto> categories2 = jpaQueryFactory
.select(Projections.bean(SampleDto.class,
qCategories.categoryname,
qCategories.description)
).from(qCategories)
.fetch();
필드 직접 접근법
Projections 의 fields 메서드를 활용하는 방법으로, 지정한 클래스의 필드에 값을 직접 주입하는 방식이다.
@Autowired 처럼 동작하기 때문에 컬럼명과 필드명을 올바르게 일치시켜야 값이 제대로 주입된다.
마찬가지로 불변 객체엔 값을 제대로 주입할 수 없다.
List<SampleDto> categories3 = jpaQueryFactory
.select(Projections.fields(SampleDto.class,
qCategories.categoryname,
qCategories.description)
).from(qCategories)
.fetch();
생성자 접근법
Projections 의 consturctor 메서드를 활용하는 방법으로, 지정한 클래스의 적절한 생성자를 찾아 값을 넣는 방식이다.
참고로 constructor 메서드의 내부를 살펴보면 전달받은 파라미터를 각각 클래스타입 배열과 값 리스트로 나누어 활용한다.
파라미터의 순서가 정확하지 않는 경우, 의도와 다르게 값이 들어갈 수도 있고, 오류 발생 시 런타임 오류가 발생한다.
따라서 생성자에 명시한 파라미터의 순서를 정확히 맞춰 값을 넘겨주어야 한다.
List<SampleDto> categories4 = jpaQueryFactory
.select(Projections.constructor(SampleDto.class, qCategories.categoryname, qCategories.description)
).from(qCategories)
.fetch();
@QueryProjection
컴파일 시점에 Q클래스를 만들어 활용하는 방식이다.
이 방식 또한 생성자를 활용하는데, 차이점은 본 클래스의 생성자가 아닌 Q클래스의 생성자를 활용한다는 점이다.
따라서 기존 생성자 방식에서 잡지 못했던 오류를 컴파일 과정에서 발견할 수 있으므로 생성자 접근법과 비교했을 때 더 안정적이라고 볼 수 있다.
그러나 QueyDsl이 제공하는 아노테이션인 만큼 의존성 관리에 주의가 필요하다.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class SampleDto {
private Long categoryid;
private String categoryname;
private String description;
@QueryProjection
public SampleDto(String categoryname, String description) {
this.categoryname = categoryname;
this.description = description;
}
}
List<SampleDto> categories5 = jpaQueryFactory
.select(new QSampleDto(qCategories.categoryname, qCategories.description))
.from(qCategories)
.fetch();
참고로 DTO로 조회한 것들은 영속성 관리 대상이 아니다.
참고
http://querydsl.com/static/querydsl/4.0.1/reference/ko-KR/html_single/#d0e2002
https://pyoungt.tistory.com/215
https://velog.io/@bagt/QueryDsl-DTO-Projection