관리 메뉴

Life goes slowly...

[JPA] JPA ExpressionUtils 서브쿼리로 연산방법 본문

프로그래밍/Java

[JPA] JPA ExpressionUtils 서브쿼리로 연산방법

빨강소 2026. 1. 28. 20:59
728x90
반응형

  ExpressionUtils로 산술 연산하기 

잘못된 시도

ExpressionUtils.as(subQuery1, "a")
    .subtract(ExpressionUtils.as(subQuery2, "b"));
    
-- ExpressionUtils는 계산용 API가 아니기 때문에 불가능

 

올바른 산술 연산 방법 (NumberExpression)

 

 - 서브쿼리끼리 “계산”을 하려면, 각각을 JPAExpressions로 만들고, select 절에서는 ExpressionUtils.as로 alias를 주고, where/조건절에서는 서브쿼리 Expression을 그냥 연산에 넣어서 사용

 

 기본 개념 정리

  • Querydsl(JPA)에서 서브쿼리는 select 절, where 절에서만 사용 가능.
  • select 절:
    • ExpressionUtils.as(JPAExpressions.select(...), "별칭") 으로 DTO 필드에 매핑.
  • where 절:
    • JPAExpressions.select(...) 를 바로 비교/연산에 사용 (ExpressionUtils.as 필요 없음).

패턴 요약

  • “서브쿼리 뽑아서 DTO 필드로 받고 싶다”
    • ExpressionUtils.as(JPAExpressions.select(...), "필드명").
  • “where 절에서 서브쿼리와 컬럼/서브쿼리 비교하고 싶다”
    • 컬럼.gt(JPAExpressions.select(...)), 컬럼.between(서브1, 서브2).
  • “서브쿼리 둘을 계산한 결과를 select로 받고 싶다”
    • NumberExpression sub1 = JPAExpressions.select(...);
    • NumberExpression sub2 = JPAExpressions.select(...);
    • ExpressionUtils.as(sub1.subtract(sub2), "필드명").

select 절에서 “서브쿼리끼리 연산 결과” 뽑기

두 서브쿼리 결과의 차이/합 등을 DTO로 받고 싶다면, 먼저 연산 Expression을 만든 뒤 그걸 ExpressionUtils.as로 감쌉니다.

 

NumberExpression<Integer> minPrice = JPAExpressions
    .select(orderSub.price.min())
    .from(orderSub);

NumberExpression<Integer> maxPrice = JPAExpressions
    .select(orderSub.price.max())
    .from(orderSub);

// max - min 값(스프레드)을 DTO에 담기
List<PriceSpreadDto> result = queryFactory
    .select(Projections.fields(PriceSpreadDto.class,
        ExpressionUtils.as(
            maxPrice.subtract(minPrice),  // 서브쿼리끼리 연산
            "spread"
        )
    ))
    .from(order)
    .fetch();

select 절: 서브쿼리 뽑고, 별칭 주기

  • 첫 번째 인자: JPAExpressions.select(...) 로 만든 서브쿼리 Expression.
  • 두 번째 인자: DTO의 필드명 "studentCount".

이 패턴을 반복해서 여러 서브쿼리 값을 DTO에 동시에 넣을 수 있습니다

public List<AcademyStudentCountDto> findAllStudentCount() {
    return queryFactory
        .select(Projections.fields(AcademyStudentCountDto.class,
            academy.name.as("academyName"),
            ExpressionUtils.as(               // 서브쿼리 결과에 별칭
                JPAExpressions
                    .select(student.id.count())
                    .from(student)
                    .where(student.academy.eq(academy)),
                "studentCount"
            )
        ))
        .from(academy)
        .fetch();
}

where 절: 서브쿼리끼리 연산(>, <, between 등)

where 절에서는 서브쿼리 Expression끼리 바로 연산하면 됩니다.

1. 컬럼 vs 서브쿼리

board.views  JPAExpressions.select(...).from(...)  gt, goe, lt 등으로 비교.

// 조회수 > 전체 평균 조회수
List<Board> result = queryFactory
    .selectFrom(board)
    .where(board.views.gt(
        JPAExpressions
            .select(subBoard.views.avg())
            .from(subBoard)
    ))
    .fetch();

2. 서브쿼리 vs 서브쿼리 (예: between)

서브쿼리 둘을 만들어서 between 등에 넣을 수 있습니다. (DB가 스칼라 서브쿼리 연산을 허용하면 사용 가능)

  • between(lower, upper) 에 각각 서브쿼리 Expression을 그대로 넣는 방식입니다.

다른 예로 gt, lt, between, loe, goe 등 수치 연산 메서드에 서브쿼리들을 인자로 넘겨서 “서브쿼리 간 연산”을 표현할 수 있습니다.

NumberExpression<Integer> lower = JPAExpressions
    .select(orderSub.price.min())
    .from(orderSub);

NumberExpression<Integer> upper = JPAExpressions
    .select(orderSub.price.max())
    .from(orderSub);

List<Order> result = queryFactory
    .selectFrom(order)
    .where(order.price.between(lower, upper))
    .fetch();

 

728x90
반응형
Comments