BackEnd/Spring Boot

Spring Data JPA Projection

연향동큰손 2025. 10. 19. 17:02

Projection이란?

Projection은 DB에서 엔티티 전체를 조회하는 대신 필요한 필드만 선택해서 조회하는 방식을 말한다.

 

스프링 공식 문서를 확인해보면 이런 예시가 나온다.

 

Projections :: Spring Data JPA

Spring Data query methods usually return one or multiple instances of the aggregate root managed by the repository. However, it might sometimes be desirable to create projections based on certain attributes of those types. Spring Data allows modeling dedic

docs.spring.io

class Person {

  @Id UUID id;
  String firstname, lastname;
  Address address;

  static class Address {
    String zipCode, city, street;
  }
}

interface PersonRepository extends Repository<Person, UUID> {

  Collection<Person> findByLastname(String lastname);
}

위 코드와 같이 lastname으로 조회를 하게 되면, Person 엔티티 전체(id, firstname, lastname, address)를 반환하게 된다.

왜냐하면, 받을 필요가 없는 필드가 있더라도 내부적으로 SELECT * FROM Person이 실행되기 때문이다.

 

하지만 JPA Projection을 사용하면 원하는 필드만 검색 결과로 불러올 수 있게 된다.

이를 통해 다양한 이점을 얻을 수 있게된다.

  1. 필요한 컬럼만 SELECT 하기 때문에 조회 성능 향상
  2. 외부로 노출할 필드만 선택하기 때문에 민감 정보 노출 위험이 감소
  3. 조회 전용 구조를 분리해두면, 엔티티 변경이 있어도 조회 로직에 영향이 적음

Projection 방법

 

1. 인터페이스 기반 Projection

엔티티에서 조회하고 싶은 필드들만 선택적으로 가져오기 위해 그 필드들을 모아둔 인터페이스를 정의하는 방식이다.

get필드명() 형태로 메서드를 정의하면,메서드 이름과 동일한 엔티티 필드에 자동으로 매핑되어 원하는 필드만 조회할 수 있다.

 

이때 get 뒤에 오는 필드명의 첫 글자는 반드시 대문자로 작성해야 엔티티의 필드와 정확히 매칭된다.

interface NamesOnly {

  String getFirstname();
  String getLastname();
}

interface PersonRepository extends Repository<Person, UUID> {

  Collection<NamesOnly> findByLastname(String lastname);
}

 

동작 과정

  1. JPA가 Repository 메서드를 분석하여 필요한 필드만 포함하는 JPQL을 생성
  2. 변환된 JPQL을 기반으로 필요한 필드만 조회하는 SQL 실행
  3. 조회한 데이터를 Projection Interface의 메서드와 매칭하여 프록시 객체를 자동으로 생성
  4. 프록시 객체가 Projection Interface를 구현하고 있음 → getXxx() 호출 시 값이 반환됨

 

2. 열린 프로젝션(Open Projection)

@Value를 이용해서 엔티티의 여러 필드를 조합하거나 가공해서 반환하는 Projection 방식이다.

target.필드로 원하는 필드를 가지고와서 필요에따라 조작이 가능하다.

interface NamesOnly {

  @Value("#{target.firstname + ' ' + target.lastname}")
  String getFullName();
  …
}

 

하지만 Open Projection 방식은 원하는 필드만 가지고오는 Interface Projection과 다르게 JPA가 엔티티 전체(SELECT * FROM person;)를 메모리로 가져온 다음 그 객체에서 값을 꺼내 @Value 식을 실행하기 때문에 쿼리 최적화를 적용할 수 없다.

 

 

3. DTO Projection

DTO Projection은JPA에서 엔티티 대신 직접 만든 클래스(DTO)로 필요한 필드만 담아서 조회하는 방식이다.

public class PersonDto {
    private final String firstname;
    private final String lastname;
    private final String city;

    public PersonDto(String firstname, String lastname, String city) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.city = city;
    }

    public String getFirstname() { return firstname; }
    public String getLastname() { return lastname; }
    public String getCity() { return city; }
}


public interface PersonRepository extends JpaRepository<Person, UUID> {

    @Query("""
        SELECT new com.example.dto.PersonDto(p.firstname, p.lastname, p.address.city)
        FROM Person p
        WHERE p.lastname = :lastname
    """)
    List<PersonDto> findPersonDtoByLastname(@Param("lastname") String lastname);
}

 

동작 과정

  1. 쿼리 실행 시 JPA가
    SELECT new com.example.dto.PersonDto(...) 부분을 보고 PersonDto(String firstname, String lastname, String city)생성자를 호출
  2. DB에서 가져온 각 row 마다 new PersonDto("홍길동", "Kim", "서울") 이런 식으로 DTO 객체가 바로 생성됨
  3. 반환 결과는 완전히 엔티티와 독립된 DTO List