Specifications
와Query By Example
은 실무에서 거의 사용하지 않고Querydsl
로 대체되기 때문에 생략!
Projections
]엔티티 대신
DTO
를 편리하게 조회할 때 사용한다.
전체 엔티티 대신 회원 이름만 조회하고 싶은 경우를 가정해보자!
Closed Projections
public interface UsernameOnly {
String getUsername();
}
getter
형식으로 지정하면 해당 필드만 선택해서 조회한다. (Projection
)public interface MemberRepositry ... {
List<UsernameOnly> findProjectionsByUsername(@Param("username") String username);
}
username
만 조회된 것을 확인할 수 있다!Open Projections
public interface UsernameOnly {
@Value("#{target.username + ' ' + target.age}")
String getUsername();
}
SpEL
문법도 제공한다.SpEL
문법을 사용하면 DB에서 엔티티 필드를 다 조회해온 다음에 계산한다Projection
public class UsernameOnlyDto {
private final String username;
public UsernameOnlyDto(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
DTO
형식도 가능하다.Projections
<T> List<T> findProjectionsByUsername(@Param("username") String username, Class<T> type);
Generic type
을 주면 동적으로 프로젝션 데이터를 변경할 수 있다.public interface NestedClosedProjection {
String getUsername();
TeamInfo getTeam();
interface TeamInfo {
String getName();
}
}
member
는 username
만, team
은 모두 조회되었다.🚫 프로젝션 사용 시 주의할 점
- 프로젝션 대상이
root
엔티티면, JPQL SELECT절 최적화가 가능하다!- 프로젝션 대상이
root
가 아니면,
LEFT OUTER JOIN
처리- 모든 필드를 SELECT해서 엔티티로 조회한 뒤에 계산한다.
📌 정리!
- 프로젝션 대상이
root
인 경우projection
이 유용하지만!
root
엔티티를 넘어가면 JPQL SELECT 최적화가 안된다.
→ 즉, 실무의 복잡한 쿼리를 해결하기에는 한계가 있다!- 실무에서는 단순할 때만 사용하고, 조금만 복잡해지면
QueryDSL
을 사용하자!
가급적 네이티브 쿼리를 사용하지 않는 것이 좋다.
최신 버전에서 스프링 데이터Projections
가 지원되므로 이 기능을 사용하자!
Object[]
Tuple
DTO
(스프링 데이터 인터페이스 Projections
지원)Sort
파라미터를 통한 정렬이 정상 동작하지 않을 수 있다. (믿지 말고 직접 처리)public interface MemberRepository extends JpaRepository<Member, Long> {
@Query(value = "select * from member where username = ?", nativeQuery = true)
Member findByNativeQuery(String username);
}
@SqlResultSetMapping
→ 복잡Hibernate ResultTransformer
를 사용해야 한다. → 복잡JdbcTemplate
or myBatis
권장Projections
활용예) 스프링 데이터 JPA 네이티브 쿼리 + 인터페이스 기반 Projections
활용
@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);