스프링 데이터 JPA Projections 및 Native Query

구본식·2023년 3월 8일
0

Spring Data JPA

목록 보기
8/8
post-thumbnail
post-custom-banner

DB의 엔티티 대신 필요한 속성(DTO)을 조회할 때 사용하는 것을 Projection이라고 한다.

Spring Data JPA에서 Projection을 하는 방법 정리하고자 한다.

1. 인터페이스 기반 Closed Projections

Getter등의 프로퍼티 형식의 인터페이스를 제공하면, 구현체는 스프링 데이터 JPA가 제공해준다.

예시를 통해 방법을 알아보자.

메소드 이름 쿼리 방식 사용

public interface UsernameOnly {
    String getUsername();
}

public interface MemberRepository extends JpaRepository<Member, Long> , MemberRepositoryCustom {
    //Spring Data JPA Projections
    List<UsernameOnly> findProjectsByUsername(String username); //인터페이스 기반 close projections
}

조회를 원하는 속성들의 집합을 인터페이스로 만든다.

get+(필드명)으로 메소드를 정의하면 된다. (필드명의 맨앞은 대문자로 적어준다.)

원하는 속성만 먼저 파악이 가능하므로 select 쿼리 최적화가 가능하다.

Named 쿼리 방식 사용(JPQL)

public interface UsernameOnly {

    String getUsername();
    String getTeamName();
}

public interface MemberRepository extends JpaRepository<Member, Long> , MemberRepositoryCustom {
    //Spring Data JPA Projections
    @Query("select m.username as username, t.name as teamName " +
            "from Member m inner join m.team t " +
            "where m.username=:username")
    List<UsernameOnly> findProjectsByUsername(@Param("username") String username); //JPQL + 인터페이스 기반 close projections
}

Named 쿼리 방식으로도 사용이 가능하다. 원하는 속성만을 지정하면 된다.

마찬가지로 원하는 속성만을 지정하면 되므로 select 쿼리 최적화가 가능하다.

중첩 구조 처리 방식 사용

public interface UsernameOnly {

    String getUsername();
    TeamInfo getTeam();
    
    interface TeamInfo {
        String getName();
    }
}

public interface MemberRepository extends JpaRepository<Member, Long> , MemberRepositoryCustom {
    //Spring Data JPA Projections
    List<UsernameOnly> findProjectsByUsername(String username); //인터페이스 기반 close projections
}

스프링 데이터 JPA중첩 구조 방식도 지원한다.
코드를 보게 되면 직관적으로 의미하는것을 쉽게 알 수 있을 것이다.

User 엔티티는 원하는 속성만 가져오게 되지만, Team 엔티티는 모든 속성을 모두 가지고 오고 난 후 필요한 속성만을 처리하게 된다.

즉, 정리하자면 아래와 같다.

  • 프로젝션 대상이 root 엔티티면 select 최적화가 가능하다. (여기선 User)
  • 프로젝션 대상이 root 엔티티가 아니면 select 최적화가 안된다. (여기선 Team)
  • 프로젝션 대상이 root 엔티티가 아니면 LEFT OUTER JOIN으로 처리
  • 모든 필드를 검색한 후 필요한 속성을 계산

2. 인터페이스 기반 Open Projections

@Value 어노테이션을 사용하여 SpEL 문법을 사용할 수 있다.

public interface UsernameOnly {
    @Value("#{target.username + ' ' + target.age + ' ' + target.team.name}")
    String getUsername();
}

public interface MemberRepository extends JpaRepository<Member, Long> , MemberRepositoryCustom {
    //Spring Data JPA Projections
   List<UsernameOnly> findProjectsByUsername(String username);   //인터페이스 기반 open projections
}

target으로 지정한 속성들을 조합해서 새로운 반환 속성 값을 만들 수 있다.

@Value 어노테이션에 들어가는 표현식을 SpEL문법이라고 하는데 이 방식을 사용하면,
DB에서 엔티티의 필드를 모두 조회해온 다음에 계산을 하게 된다. (여기선 User,Team)

그렇기 때문에 select 쿼리 최적화가 불가능하다!.

참고로 인터페이스 기반 Projections을 사용할 때 select 절에 지정한 필요한 속성들의 집합의 객체로 Proxy 객체가 들어가게 된다.


3. 클래스 기반 Projection

클래스 기반으로도 Projection을 제공한다. 재사용성을 위한 좋은 방법이다.

메소드 이름 쿼리 방식 사용

@Data
public class UsernameOnlyDto {

    private String username;
    private Long id;
    private Team team;
    public UsernameOnlyDto(String username, Long id, Team team){
        this.username = username;
        this.id = id;
        this.team = team;
    }
}


public interface MemberRepository extends JpaRepository<Member, Long> , MemberRepositoryCustom {
   //Spring Data JPA Projections
   List<UsernameOnlyDto> findProjectsDtoByUsername(String username);  //클래스 기반 projections
}

클래스 기반 Projection생성자의 파라미터 이름으로 매칭을 시켜주기 때문에,
생성자생성자 파라미터 이름이 같아야 된다.

Named 쿼리 방식 사용(JPQL)

JPQL를 사용한 클래스 기반 프로젝션을 해주기 위해서는, select 문에 dto 경로까지 지정해주어야 한다.

@Data
public class UsernameOnlyDto {

    private String username;
    private String teamName;
    public UsernameOnlyDto(String username, String teamName) {
        this.username = username;
        this.teamName = teamName;
    }
}

public interface MemberRepository extends JpaRepository<Member, Long> , MemberRepositoryCustom {
   //Spring Data JPA Projections
   @Query("select new study.springdatajpa.entity.projections.UsernameOnlyDto(m.username, t.name)" +
            "from Member m left join m.team t " +
            "where m.username=:username")
    List<UsernameOnlyDto> findProjectsDtoByUsername(@Param("username") String username); //JPQL + 클래스 기반 projections
}

4. 네이티브 쿼리

스프링 데이터 JPA 기반의 네이티브 쿼리는 아래와 같은 특징이 있다.

  • 페이징 지원
  • 반환 타입
    • Object[]
    • Tuple
    • DTO(스프링 데이터 인터페이스 Projections 지원)
  • 제약
    • Sort 파라미터를 통한 정렬이 정상 동작하지 않을 수 있음(믿지 말고 직접 처리)
    • JPQL처럼 애플리케이션 로딩 시점에 문법 확인 불가
    • 동적 쿼리 불가
public interface MemberProjection {

    Long getId();
    String getUsername();
    String getTeamName();
}

public interface MemberRepository extends JpaRepository<Member, Long> , MemberRepositoryCustom {
    //네이티브 쿼리 + 인터페이스 기반 Projections
    @Query(value = "select m.member_id as id, m.username, t.name as teamName " +
            "from member m left join team t on m.team_id = t.team_id",
            countQuery = "select count(*) from member",
            nativeQuery = true)
    Page<MemberProjection> findByNativeProjections(Pageable pageable);
}

대부분 네이티브 쿼리를 사용할때에는 반환값으로 DTO를 사용하는 경우가 많다고 한다.

스프링 데이터 JPA의 Projections 을 사용하면 네이티브 쿼리에서도 DTO 반환을 편리하게 할 수 있다고 한다.

참고로 복잡한 쿼리일 때는 Querydsl를 사용하는 것이 가장 좋다고 한다.

profile
백엔드 개발자를 꿈꾸며 기록중💻
post-custom-banner

0개의 댓글