본문 바로가기
Spring

QueryDsl의 Projections 활용

by seeker00 2025. 2. 26.

QueryDsl의 Projections 활용

  • Projections는 QueryDsl에서 제공하는 쿼리 결과의 dto 매핑을 위한 기능입니다.
  • 불필요한 컬럼의 조회를 방지해 쿼리 조회시 발생하는 통신 비용을 줄일 수 있습니다.
  • Projections에는 다음과 같은 네 가지 방법이 있습니다. 저희 조는 그 중에서도 @QueryProjection 을 활용해보았습니다.
    • @QueryProjection
    • Projections.bean(..)
    • Projections.field(..)
    • Projections.construct(..)

@QueryProjection

  • Dto 생성자에 @QueryProjection 어노테이션을 붙여, Q-Type 클래스를 만들어 사용하는 방법입니다.
    • 컴파일 시점에 에러를 잡아낼 수 있어 안전하다는 장점이 있습니다.
      • 해당 장점을 고려하여, 저희 팀은 @QueryProjection을 활용해보았습니다.
    • 그러나 Dto도 Q-Type을 생성한다는 점과 Dto가 QueryDSL에 대한 의존성을 갖게 된다는 단점이 존재합니다.
    @Override
    public Page<ReviewResponseDto> searchReviewByUser(UUID userId, Pageable pageable) {

        List<ReviewResponseDto> dtoList = queryFactory
            .select(new QReviewResponseDto(review.reviewId,
                new QReviewUserResponseDto(user.userId, user.username, user.nickname),
                new QReviewRestaurantResponseDto(restaurant.resId, restaurant.resName),
                review.reviewScore, review.reviewContent, review.reviewImageUrl, review.order.orderId, review.isPublic,
                review.createdAt)
            )
            .from(review)
            .leftJoin(user).on(user.userId.eq(review.user.userId))
            .leftJoin(restaurant).on(restaurant.resId.eq(review.restaurant.resId))
            .where(
                eqUserId(userId),
                review.deletedAt.isNull()
            )
            .orderBy(createOrderSpecifiers(pageable.getSort()))
            .offset(pageable.getOffset())
            .limit(validatePageSize(pageable.getPageSize()))
            .fetch();

        //... 생략

        return PageableExecutionUtils.getPage(dtoList, pageable, () -> countQuery.fetchOne());
    }

Projections.bean(..)

  • Setter와 기본 생성자가 필요합니다.
    • Dto 객체는 불변 객체로 사용하는 것을 지향하기 때문에 Setter 를 사용한다는 점에서 아쉬운 부분이 있습니다.
List<MemberDto> result = queryFactory
          .select(Projections.bean(MemberDto.class,
                  member.username,
                  member.age))
          .from(member)
          .fetch();

Projections.field(..)

  • 기본 생성자가 필요하며, Dto의 필드명과 Projection의 컬럼명이 일치하면 값 매핑이 동작합니다.
    • 만약 필드명과 컬럼명이 다를 경우에는 별칭(alias)을 사용하여 매핑할 수 있습니다.
List<MemberDto> result = queryFactory
          .select(Projections.fields(MemberDto.class,
                  member.username.as("name"),
                  member.age))
          .from(member)
          .fetch();

Projections.construct(..)

  • 매핑할 필드를 매개변수로 받는 생성자가 필요합니다. 생성자를 사용하여 필드에 매핑시켜 주는 방법입니다.
List<MemberDto> result = queryFactory
          .select(Projections.constructor(MemberDto.class,
                  member.username,
                  member.age))
          .from(member)
          .fetch()
 }

참고 페이지