프로젝션: select 대상 지정
(즉, select 절에 뭘 가져올지 대상을 지정하는 것)
프로젝션 대상이 하나일 때는 그냥 그 대상에 맞춰서 반환 타입을 주면 된다.
➡️ 프로젝션 대상이 하나면 타입을 명확하게 지정할 수 있음
프로젝션 대상이 둘 이상이라면 튜플이나 DTO로 조회해야 한다.
com.querydsl.core.Tuple
)⚠️ 튜플은 리포지토리 안에서 필요할 때만 사용하고 그 이외에는 DTO를 사용하는 것을 추천함
: 순수 JPA에서 DTO 조회하기
@Data
@NoArgsConstructor // 기본 생성자 대신
public class MemberDto {
private String username;
private int age;
// public MemberDto() { // @NoArgsConstructor로 대신할 수 있음
// }
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
List<MemberDto> result = em.createQuery("select new study.Querydsl.dto.MemberDto(m.username, m.age) " +
"from Member m", MemberDto.class)
.getResultList();
결과를 DTO 반환할 때 사용한다.
그리고
이렇게 세 가지 방법을 지원한다.
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class, // lombok -> getter, setter를 통한
member.username,
member.age))
.from(member)
.fetch();
bean
: setter로 인젝션 해줌MemberDto.class
: 타입을 지정member.username, member.age
: 타입 지정 다음 꺼내올 값들을 나열하기 List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class, // getter, setter를 무시하고 field에 값을 바로 꽂아버림
member.username,
member.age))
.from(member)
.fetch();
bean
대신 fields
로 바꿔주면 됨: 프로퍼티나 필드 접근 생성 방식에서 이름이 다를 때 해결 방안이다.
(UserDto에는 username이 아닌 name으로 해놨음)
List<UserDto> result = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
member.age))
.from(member)
.fetch();
ExpressionUtils.as(source, alias)
: 필드나, 서브 쿼리 별칭 적용username.as("memberName")
: 필드에 별칭 적용 List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class, // constructor는 타입을 맞춰야함
member.username,
member.age))
.from(member)
.fetch();
: 제일 깔끔한 궁극의 방법이지만 단점이 있음
(생성자를 사용하는 방식에서는 @QueryProjections
도 지원한다.)
이 방법은 컴파일러로 타입을 체크할 수 있어 가장 안전하다.
다만, DTO에 QueryDSL 어노테이션을 유지해야 하는 것과 DTO까지 Q파일을 생성해야 하는 단점이 있다.
➡️ memberDto가 QueryDSL에 의존성을 가지게 되어버린다.
생성자 사용하는 방법이랑 차이점: 생성자 사용하는 방법은 컴파일 시점에야 에러를 잡을 수 있지만, @QueryProjection
은 코드 작성시 에러를 바로 알 수 있음