QueryDsl을 도입한 프로젝트에서 Select절을 사용할 때 불필요한 컬럼까지 조회를 하는 경우가 생기곤 한다.
public Product findProductWithDetailsAndPhotos(Long productId) {
QProduct qProduct = QProduct.product;
QProductDetail qProductDetail = QProductDetail.productDetail;
QProductDescriptionPhoto qProductDescriptionPhoto = QProductDescriptionPhoto.productDescriptionPhoto;
QOption qOption = QOption.option;
return queryFactory
.selectFrom(qProduct)
.leftJoin(qProduct.productDetail, qProductDetail).fetchJoin()
.leftJoin(qProduct.productDescriptionPhotos, qProductDescriptionPhoto).fetchJoin()
.leftJoin(qProduct.options, qOption).fetchJoin()
.where(qProduct.productId.eq(productId))
.fetchOne();
}
product 상세 정보를 조회하는 부분에서 product의 등록일자 또는 수정일자같이 상품구매를 위한 상품 상세정보 조회에서는 불필요한 필드들이 존재한다.
이렇게 불필요한 컬럼 조회는 클라이언트에게 데이터를 전송하는 과정에 네트워크 부하가 생길 가능성이 높아진다.
따라서 나는 필요한 컬럼만 조회하도록 Projections를 사용하기로 했다.
Projection은 select 절에서 어떤 컬럼들을 조회할지 대상을 지정하는 것을 말한다.
Projection 대상이 하나일 경우는 타입이 명확하기 때문에 해당 Generic Type이 해당 컬럼 타입에 맞게 지정된다.
public DescriptionResponse findProductWithDetailsAndPhotos(Long productId) {
return queryFactory
.select(Projections.bean(DescriptionResponse.class,
product.name,
product.price,
product.type,
product.photo,
product.productDetail.description.as("description"),
product.productDescriptionPhotos.as("descriptionPhotos"),
product.productDetail.as("hasPhoto"),
product.productDetail.productName.as("productName"),
product.options,
product.brand))
.from(product)
.leftJoin(product.productDetail).fetchJoin()
.leftJoin(product.productDescriptionPhotos).fetchJoin()
.leftJoin(product.options).fetchJoin()
.where(product.productId.eq(productId))
.fetchOne();
}
우선 상세정보를 조회할 때 필요한 컬럼들을 정리하였고, 필요한 컬럼을 조회하도록 수정했다.
하지만 Projections.bean은 setter기반으로 작동된다.
Spring Boot를 사용하는 백엔드 개발자라면 setter의 사용을 지양하자는 말이 있다.
따라서 setter기반으로 작동되지 않는 방법도 고려해봐야 한다.
@QueryProjection은 생성자를 통해 DTO를 조회하는 방법과 함께 사용되는 방법이다.
사용하려면 dto 클래스의 생성자에 @QueryProcjetion 어노테이션을 추가해야한다.
어노테이션을 추가하면 dto의 QClass가 생성되고 QClass를 사용하여 결과를 낸다.
public DescriptionResponse findProductWithDetailsAndPhotos(Long productId) {
return queryFactory
.select(descriptionResponse)
.from(product)
.leftJoin(product.productDetail).fetchJoin()
.leftJoin(product.productDescriptionPhotos).fetchJoin()
.leftJoin(product.options).fetchJoin()
.where(product.productId.eq(productId))
.fetchOne();
}
@QueryProcjetion을 사용한 방법은 컴파일러를 통해 타입을 확인할 수 있고, 가독성이 엄청 늘어난다. (마치.. Projections를 안쓴 느낌같이) 하지만 dto 클래스마다 모두 Qclass가 생성되기 때문에 관리가 어려울 수 있다는 단점이 있다.
이부분은 트레이드 오프라고 생각해서 지금 진행중인 프로젝트는 dto클래스가 상당히 많이 나눠져있어 setter를 지양하고 싶은 마음도 있지만 setter를 지양하는 이유인 "일관성"을 벗어나지 않는다고 생각해서 Projections.bean을 사용했다.
그러나, Projections.bean을 사용하는데 일관성이 떨어지게 된다면 수정해야 할 것이다. 나는 최대한 일관성을 무너뜨리지 않고 주의해서 개발을 진행하려 한다.