Spring Data JPA - Projections

2022. 8. 3. 21:30·공부/JPA
목차
  1. Projections
  2. 인터페이스 기반의 Closed Projections
  3. 인터페이스 기반의 Open Projections
  4.  
  5. 클래스 기반의 Projections
  6. 동적 Projections
  7. 중첩 구조 처리
  8. 주의
  9. 정리
반응형

출처 : 인프런 실전! 스프링 데이터 JPA

Spring Data JPA 게시글은 대부분 인프런의 김영한님의 강의인 '실전! 스프링 데이터 JPA' 기반으로 내용을 정리했습니다.

 

Projections

이 기능은 약간의 도움이 될 때가 있어 앞 부분보다는 잘 듣는 게 좋다.

엔티티 대신에 DTO를 편리하게 조회할 때 사용한다. 예를 들면 Member 엔티티가 아니라 Member의 이름만 조회하고 싶을 때 유용하다.

 

 

인터페이스 기반의 Closed Projections

  • UsernameOnly.interface
public interface UsernameOnly {

    String getUsername();
}
  • MemberRespository.interface
List<UsernameOnly> findProjectionsByUsername(@Param("username") String username);

특이한 점은 List 제네릭에 Member가 아닌 UsernameOnly 인터페이스가 들어간다. 이렇게 세팅해주면 사용준비 끝!

  • MemberRepositoryTest.class
@Test
public void projections() {
    Team teamA = new Team("teamA");
    em.persist(teamA);

    Member m1 = new Member("m1", 0, teamA);
    Member m2 = new Member("m2", 0, teamA);
    em.persist(m1);
    em.persist(m2);

    em.flush();
    em.clear();

    List<UsernameOnly> result = memberRepository.findProjectionsByUsername("m1");
    for (UsernameOnly usernameOnly : result) {
        System.out.println("usernameOnly = " + usernameOnly);
    }
}
    select
        member0_.username as col_0_0_ 
    from
        member member0_ 
    where
        member0_.username=?
usernameOnly = org.springframework.data.jpa.repository.query
			.AbstractJpaQuery$TupleConverter$TupleBackedMap@13cc3984

반환 된 result를 for문 돌리면서 usernameOnly를 찍어보면 이상한 것이 출력된다. 이렇게 인터페이스로 반환 타입을 지정하면 스프링 데이터 JPA에서 프록시 같은 기술들을 가지고 가짜 객체를 만든다. 즉 개발자가 인터페이스만 정의하면 구현체는 스프링 데이터 JPA가 알아서 만든다.

 

 

인터페이스 기반의 Open Projections

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

위와 같이 사용하면 Open Projections이다. Member(target)의 username과 age 데이터를 가져와서 Value에 있는 문자열 형식(SpEL)처럼 만든 후 리턴해준다.

select
    member0_.member_id as member_i1_1_,
    member0_.create_by as create_b2_1_,
    member0_.create_date as create_d3_1_,
    member0_.last_modified_by as last_mod4_1_,
    member0_.last_modified_date as last_mod5_1_,
    member0_.age as age6_1_,
    member0_.team_id as team_id8_1_,
    member0_.username as username7_1_ 
from
    member member0_ 
where
    member0_.username=?
usernameOnly = m1 0

age 데이터까지 가져와야하기 때문에 Member의 모든 데이터를 가져온다. 엔티티를 일단 가져오고 지정한 데이터를 가져와서 SpEL 형식처럼 뿌려주는 것이다. 그래서 JPQL SELECT 최적화가 되지 않는다.

 

 

클래스 기반의 Projections

  • UsernameOnlyDto.class
public class UsernameOnlyDto {

    private final String username;

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

    public String getUsername() {
        return username;
    }
}

클래스 기반에서 중요한 건 생성자이다. 생성자의 파라미터 명으로 Projections이 동작한다. 조회하려는 엔티티의 필드 이름과 다르면 동작하지 않으므로 주의해야 한다.

  • MemberRepository.interface
List<UsernameOnlyDto> findClassProjectionsByUsername(@Param("username") String username);
  • MemberRepositoryTest.class
@Test
public void ClassProjections() {
    Team teamA = new Team("teamA");
    em.persist(teamA);

    Member m1 = new Member("m1", 0, teamA);
    Member m2 = new Member("m2", 0, teamA);
    em.persist(m1);
    em.persist(m2);

    em.flush();
    em.clear();

    List<UsernameOnlyDto> result = memberRepository.findClassProjectionsByUsername("m1");
    for (UsernameOnlyDto usernameOnly : result) {
        System.out.println("usernameOnly = " + usernameOnly.getUsername());
    }
}
    select
        member0_.username as col_0_0_ 
    from
        member member0_ 
    where
        member0_.username=?
usernameOnly = m1

 

 

동적 Projections

  • MemberRepository.interface
<T> List<T> findGenericProjectionsByUsername(@Param("username") String username, Class <T> type);

제네릭을 사용하면 동적으로 Projections의 데이터를 변경할 수 있다.

  • MemberRepositoryTest.class
List<UsernameOnlyDto> result =
        memberRepository.findGenericProjectionsByUsername("m1", UsernameOnlyDto.class);

클래스 타입을 매개변수로 넘겨주면 알아서 딱 잘 맞춰서 동작한다.

 

 

중첩 구조 처리

  • NestedClosedProjections.interface
public interface NestedClosedProjections {
    
    String getUsername();
    TeamInfo getTeam();
    
    interface TeamInfo {
        String getName();
    }
}

인터페이스 안에 TeamInfo란 인터페이스를 하나 더 만들고 Team에서 가져올 필드까지 생성해주면 Member와 Team 데이터를 가져올 수 있다.

  • MemberRepositoryTest.class
List<NestedClosedProjections> result =
        memberRepository.findGenericProjectionsByUsername("m1", NestedClosedProjections.class);

for (NestedClosedProjections nestedClosedProjections : result) {
    System.out.println("nestedClosedProjections = " + nestedClosedProjections);
}
    select
        member0_.username as col_0_0_,
        team1_.team_id as col_1_0_,
        team1_.team_id as team_id1_2_,
        team1_.create_date as create_d2_2_,
        team1_.update_date as update_d3_2_,
        team1_.name as name4_2_ 
    from
        member member0_ 
    left outer join
        team team1_ 
            on member0_.team_id=team1_.team_id 
    where
        member0_.username=?
nestedClosedProjections = org.springframework.data.jpa.repository.query
 		.AbstractJpaQuery$TupleConverter$TupleBackedMap@3733b1f4

쿼리를 잘 보면 Member의 데이터는 내가 원하는대로 username만 딱 가져왔지만 Team의 경우는 Name이 아닌 다른 데이터들도 모두 다 가져온 걸 확인할 수 있다. 중첩 구조는 제일 첫 번째 메서드(root)에 대해서만 쿼리 최적화가 되고 두 번째 메서드(getTeam)부터는 쿼리 최적화가 동작하지 않는다.

 

주의

  • 프로젝션 대상이 root 엔티티면 JPQL SELECT절 최적화가 가능하다.
  • 프로젝션 대상이 root가 아니면 
    • LEFT OUTER JOIN 발생
    • 모든 필드를 SELECT해서 엔티티로 조회한 다음에 후처리

 

정리

  • 프로젝션 대상이 root 엔티티면 유용하게 사용할 수 있다.
  • 프로젝션 대상이 root 엔티티를 넘어가면 JPQL SELECT 쿼리 최적화가 안 된다.
  • 실무의 복잡한 쿼리를 해결하기에는 한계가 있다.
  • 실무에서는 단순할 때만 사용하고, 조금 복잡해지면 QueryDSL을 사용하자.

 

반응형
저작자표시 비영리 변경금지 (새창열림)
  1. Projections
  2. 인터페이스 기반의 Closed Projections
  3. 인터페이스 기반의 Open Projections
  4.  
  5. 클래스 기반의 Projections
  6. 동적 Projections
  7. 중첩 구조 처리
  8. 주의
  9. 정리
'공부/JPA' 카테고리의 다른 글
  • Query DSL - 프로젝트 환경설정
  • Spring Data JPA - 네이티브 쿼리
  • Spring Data JPA - Query By Example
  • Spring Data JPA - Specifications(명세)
데부한
데부한
어차피 할 거면 긍정적으로 하고 싶은 개발자
    반응형
  • 데부한
    동동이개발바닥
    데부한
  • 전체
    오늘
    어제
    • 분류 전체보기 (307)
      • 방통대 컴퓨터과학과 (27)
        • 잡담 (9)
        • 3학년1학기 (17)
      • 프로젝트 및 컨퍼런스 회고 (1)
        • 프로젝트 (4)
        • 한이음 프로젝트 (0)
        • 회고 (3)
      • 공부 (165)
        • Spring (37)
        • JPA (71)
        • 인프런 워밍업 클럽_BE (10)
        • Java (6)
        • React.js (27)
        • 넥사크로 (11)
        • 기타 (3)
      • 알고리즘 (85)
        • 알고리즘 유형 (10)
        • 알고리즘 풀이 (57)
        • SQL 풀이 (18)
      • 에러 해결 (13)
      • 잡담 (7)
        • 국비교육 (2)
        • 구매후기 (5)
        • 진짜 잡담 (0)
  • 블로그 메뉴

    • Github
    • Linkedin
    • 홈
    • 방명록
    • 글쓰기
    • 관리
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    egov
    oracle
    토비의스프링부트
    Spring
    기출문제
    IT
    에러해결
    Java
    토이프로젝트
    개발자
    코딩테스트
    백준
    RESTful
    넥사크로
    SQL
    방통대
    운영체제
    react
    알고리즘
    SpringBoot를 이용한 RESTful Web Service 개발
    자바스크립트
    MSA
    프론트엔드
    전자정부프레임워크
    QueryDSL
    프로그래머스
    springboot
    JPA
    스프링부트
    인프런
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
데부한
Spring Data JPA - Projections
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.