JPA Criteria API 위에 더 깔끔한 프로젝션 레이어 구축

발행: (2026년 1월 12일 오후 11:37 GMT+9)
4 min read
원문: Dev.to

Source: Dev.to

소개

Java와 JPA를 사용해 시스템을 개발한다면, 특정 엔티티에서 일부 속성만 반환하는 쿼리를 실행해야 하는 상황을 겪어봤을 것입니다.
언뜻 보기엔 간단해 보이지만, 제대로 다루지 않으면 시스템에 불필요한 쿼리들이 쌓이거나, 절대 사용되지 않을 속성들까지 포함된 과도한 쿼리가 발생할 수 있습니다.

실제 상황에서는 개발자가 엔티티의 idname만을 조회해야 할 때가 많습니다. 시스템 규모와 복잡성이 커지면, 정확히 이 데이터를 반환하는 쿼리가 이미 존재하는지 파악하기 어려워집니다. 또 다른 경우에는 전체 엔티티를 로드하는 메서드를 재사용한 뒤, 실제로 필요한 몇 개의 속성만 추출하는 식으로 작업하게 됩니다.

ProjectionQuery는 이러한 프로젝션 기반 쿼리를 단순화하고 정리하기 위해 만들어졌으며, 애플리케이션이 진정으로 필요로 하는 데이터만을 선택적으로 조회할 수 있는 더 명확하고 표현력 있는 방법을 제공합니다.

프로젝션 정의하기

프로젝트에 의존성을 추가한 뒤, 클래스를(또는 레코드를) 만들고 프로젝션 대상이 될 필드에 어노테이션을 붙입니다.

@Projection(of = Customer.class)
public record CustomerBasicData(
        @ProjectionField Long id,
        @ProjectionField String name,
        @ProjectionField("address.city.name") String city,
        @ProjectionField("address.city.state.name") String state
) { }

이 레코드는 최종 쿼리 결과의 형태를 나타냅니다. Customer 엔티티에 얼마나 많은 속성이 있든, 데이터베이스에서는 id, name, city, state만 선택됩니다.
citystateCustomer 엔티티에 정의된 관계를 통해 가져오는 중첩 속성임을 유의하세요.

생성된 SQL (단순화 버전)

위 프로젝션에 대해 생성되는 SQL은 대략 다음과 같습니다:

SELECT
    c.id,
    c.name,
    city.name   AS city,
    state.name  AS state
FROM customer c
INNER JOIN address a   ON c.address = a.id
INNER JOIN city   ci  ON a.city = ci.id
INNER JOIN state  s   ON ci.state = s.id;

프로젝션 실행하기

간단한 실행

ProjectionProcessor processor = new ProjectionProcessor(entityManager);
List customers = processor.execute(CustomerBasicData.class);

ProjectionQuery를 활용한 고급 시나리오

ProjectionQuery는 필터, 정렬, 페이지네이션 및 기타 설정을 추가할 수 있게 해 주어 보다 복잡한 쿼리를 구성할 수 있도록 돕습니다.

ProjectionProcessor processor = new ProjectionProcessor(entityManager);

ProjectionQuery query = ProjectionQuery
    .fromTo(Customer.class, CustomerBasicData.class)
    .filter("address.city.name", ProjectionFilterOperator.EQUAL, "São Paulo")
    .order("name", OrderDirection.ASC)
    .paging(0, 20)
    .distinct();

List customers = processor.execute(query);

ProjectionQuery는 독립적으로 사용할 수도 있고, Spring Boot 애플리케이션에 통합해서 사용할 수도 있습니다.

추가 자료

자세한 내용, 추가 예제 및 전체 문서는 GitHub에 있는 프로젝트 페이지를 참고하시기 바랍니다.

Back to Blog

관련 글

더 보기 »

Lazy vs. Eager 로딩 & JPA 관계

!Forem 로고https://media2.dev.to/dynamic/image/width=65,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2...

Spring Data JPA 관계

소개 새해 복 많이 받으세요! 풀스택 여정의 지난 10일 동안, 입사 직후 프로젝트를 진행해 왔습니다. 처음에는 Re...