DB의 엔티티 대신 필요한 속성(DTO)을 조회할 때 사용하는 것을 Projection
이라고 한다.
Spring Data JPA
에서 Projection
을 하는 방법 정리하고자 한다.
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 쿼리 최적화가 가능하다.
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 엔티티는 모든 속성을 모두 가지고 오고 난 후 필요한 속성만을 처리하게 된다.
즉, 정리하자면 아래와 같다.
@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 객체가 들어가게 된다.
클래스 기반으로도 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
은 생성자의 파라미터 이름으로 매칭을 시켜주기 때문에,
생성자와 생성자 파라미터 이름이 같아야 된다.
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
}
스프링 데이터 JPA
기반의 네이티브 쿼리는 아래와 같은 특징이 있다.
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
를 사용하는 것이 가장 좋다고 한다.