프로젝션: select 절에 어떤것을 조회할 것인지 대상을 지정하는 것
List<String> result = queryFactory
.select(member.username)//하나만 존재하는 경우
.from(member)
.fetch();
📌참고: tuple은 리포지토리 계층을 넘어서서 service나 controller 까지 넘어가는 것은 좋지 않다. resultSet, tuple 모두 동일하다.
리포지토리 계층 외부로 보낼 경우에는 DTO로 변환해서 보내야한다.
package study.querydslpractice.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@ToString
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class MemberDto {
private String username;
private int age;
}
@DisplayName("findDtoByJPQL")
@Test
void findDtoByJPQL() {
//new operation 활용 방법
List<MemberDto> resultList = em.createQuery("select new study.querydslpractice.dto.MemberDto(m.username, m.age) from Member m", MemberDto.class)
.getResultList();
for (MemberDto memberDto : resultList) {
System.out.println(memberDto);
}
}
@NoArgsConstructor
public class MemberDto {
private String username;
private int age;
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
public void setUsername(String username) {
this.username = username;
}
public void setAge(int age) {
this.age = age;
}
public String getUsername() {
return username;
}
public int getAge() {
return age;
}
}
Projections.bean()
@DisplayName("findDtoBySetter")
@Test
void findDtoBySetter() {
List<MemberDto> dtoList = jpaQueryFactory
.select(Projections.bean(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
}
Projections.Fields()
@DisplayName("findDtoByField")
@Test
void findDtoByField() {
List<MemberDto> dtoList = jpaQueryFactory
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
}
@DisplayName("findDtoByConstructor")
@Test
void findDtoByConstructor() {
List<MemberDto> dtoList = jpaQueryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
}
생성자 주입의 경우 매개변수의 수와 타입순서만 맞으면 이름이 다르더라도 생성이된다
하지만 필드 주입이나, Setter 주입의 경우 별칭이 중요하기 때문에 이름을 맞춰줘야한다.
.as("별칭")
ExpressionUtils.as(source,alias)
//field 주입 as 사용
@DisplayName("findUserDtoByField")
@Test
void findUserDtoByField() {
List<UserDto> dtoList = jpaQueryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
member.age))
.from(member)
.fetch();
}
//field 주입 ExpressionUtils.as 사용
@DisplayName("findUserDto")
@Test
void findUserDto() {
QMember memberSub = new QMember("memberSub");
List<UserDto> dtoList = jpaQueryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
ExpressionUtils.as(//서브 쿼리
JPAExpressions
.select(memberSub.age.max())
.from(memberSub), "age")
))
.from(member)
.fetch();
}
- 생성자 호출이랑 비슷하지만 QeuryProjection은 컴파일시 타입체크 등의 에러를 발생시켜 준다.
@ToString
@Getter
@NoArgsConstructor
public class MemberDto {
private String username;
private int age;
@QueryProjection
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
@DisplayName("findDtoByQueryProjection")
@Test
void findDtoByQueryProjection() {
List<MemberDto> result = jpaQueryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDTO = " + memberDto);
}
}
무조건 사용하면 좋지 않다는 것 보다는 어플리케이션 전반적으로 이미 Querydsl에 의존하고 있거나 앞으로를 봤을 때 querydsl을 많이 사용할 것 같다면 QeuryProjection 방법을 사용하는 것도 좋음.