목차 (클릭시 해당 목차로 이동)
Projections
- close projection : 가져오고 싶은 부분만 딱 맞춰서 쿼리날려서 가져오기
- open projection : 일단 엔티티를 다 가져와서 애플리케이션에서 골라내기
인터페이스 기반의 close projection
만약 전체 엔티티가 아니라 회원 이름만 딱 조회하고 싶으면 보통 DTO를 사용합니다.
projections는 엔티티 대신에 DTO를 조회할때 사용하는 기능입니다.
인터페이스만 정의하면 실제 구현체는 스프링 데이터 jpa가 프록시 기술을 이용해 만들어줍니다.
package study.datajpa.repository;
import org.springframework.beans.factory.annotation.Value;
public interface UsernameOnly {
String getUsername();
}
MemberRepository
List<UsernameOnly> findProjectionsByUsername(@Param("username") String username);
이제 아래 테스트를 돌리면
@Test
public void projections() throws Exception {
//given
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();
//when
List<UsernameOnly> result = memberRepository.findProjectionsByUsername("m1");
for (UsernameOnly usernameOnly : result) {
System.out.println("usernameOnly = " + usernameOnly.getUsername());
}
//then
}
원하는 부분(Username)만 가져오는 쿼리가 나갑니다.
UsernameOnly에는 프록시가 담겨있습니다.
프록시로 구현체를 만들어 넣습니다.
인터페이스 기반의 open projection
@Value를 이용해 인터페이스에 spl문법을 적습니다.
그러면 open projection이 동작해서 엔티티를 다 가져와 애플리케이션에서 spl문법에서 target에 해당되는 부분을 변수에 넣어줍니다.
package study.datajpa.repository;
import org.springframework.beans.factory.annotation.Value;
public interface UsernameOnly {
@Value("#{target.username + ' ' + target.age}")
String getUsername();
}
동일 테스트를 진행하면,
@Test
public void projections() throws Exception {
//given
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();
//when
List<UsernameOnly> result = memberRepository.findProjectionsByUsername("m1");
for (UsernameOnly usernameOnly : result) {
System.out.println("usernameOnly = " + usernameOnly.getUsername());
}
//then
}
엔티티 정보를 모두 가져오는 open projection을 하고 username과 age를 둘다 변수에 넣어줍니다.
클래스 기반의 projection
실제 클래스가 있으니 프록시말고 실제 클래스를 넣습니다.
package study.datajpa.repository;
public class UsernameOnlyDto {
private final String username;
public UsernameOnlyDto(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
usernameOnlyDto를 찍어봤을때 프록시 클래스가 아닌 실제 클래스가 들어있습니다.
@Test
public void projectionsClass() throws Exception {
//given
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();
//when
List<UsernameOnlyDto> result = memberRepository.findProjectionsByUsername("m1");
for (UsernameOnlyDto usernameOnlyDto : result) {
System.out.println("usernameOnlyDto = " + usernameOnlyDto);
System.out.println("usernameOnlyDto.getUsername() = " + usernameOnlyDto.getUsername());
}
//then
}
동적으로 반환타입 지정도 가능합니다.
<T> List<T> findProjectionsByUsername(@Param("username") String username, Class<T> type);
중첩구조
package study.datajpa.repository;
public interface NestedClosedProjections {
String getUsername();
TeamInfo getTeam();
interface TeamInfo {
String getName();
}
}
첫 엔티티는 close projection으로 잘 가져오지만, 두번째 엔티티 부터는 엔티티 전체를 가져옵니다.
그래서 엔티티 하나 넘어가는 순간 쓰기가 좀 힘들어집니다.
결론
간단한 projection 정도는 쓸만하지만
실무에서는 QueryDSL 쓰자.
네이티브 쿼리
(JPA를 사용할땐 실무에서는 되도록 쓰지 않는 것이 좋습니다.)
스프링 데이터 JPA에서 제공하는 네이티브 쿼리 기능 입니다.
제약 3가지 때문에 잘 이용하지 않습니다.
- Sort 파라미터를 통한 정렬이 정상 동작하지 않는 경우가 있습니다.
- 애플리케이션 로딩시점에 문법오류 확인이 불가합니다.
- 동적쿼리가 불가합니다.
결론 : 네이티브 SQL을 DTO로 조회할때는 jdbcTemplate이나 myBatis를 쓰는 것이 좋습니다.
최근에 나온 괜찮은 방법
동적쿼리가 아닐때 쓰기 좋습니다.
네이티브 쿼리 -> DTO로 가져올때 projection을 이용합니다.
또한, pageable을 사용하는 것이 가능해 페이징과 정렬이 가능합니다.
@Query를 이용해 네이티브 쿼리를 적고, nativeQuery = true를 해주시면 됩니다.
Page를 사용하기 위해 필요한 countQuery도 따로 작성가능합니다.
MemberRepository
@Query(value = "select m.member_id as id, m.username, t.name as teamName " +
"from member m left join team t",
countQuery = "select count(*) from member",
nativeQuery = true)
Page<MemberProjection> findByNativeProjection(Pageable pageable);
@Test
public void nativeQuery() throws Exception {
//given
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();
//when
Page<MemberProjection> result = memberRepository.findByNativeProjection(PageRequest.of(0, 10));
List<MemberProjection> content = result.getContent();
for (MemberProjection memberProjection : content) {
System.out.println("memberProjection.getUsername() = " + memberProjection.getUsername());
System.out.println("memberProjection.getTeamName() = " + memberProjection.getTeamName());
}
//then
}
'Web > JPA' 카테고리의 다른 글
[Data JPA] EP 5. 스프링 데이터 JPA 분석 (1) | 2021.06.01 |
---|---|
[Data JPA] EP 4. 확장 기능 (0) | 2021.05.31 |
[Data JPA] EP 3. 쿼리 메소드 기능 -2 (0) | 2021.05.28 |
[Data JPA] EP 2. 쿼리 메소드 기능 (0) | 2021.05.26 |
[Data JPA] EP 1. 공통 인터페이스 기능 (0) | 2021.05.25 |