김영한님의 '실전! Querydsl'을 수강하며 정리하는 글입니다
프로젝션이란 엔티티에 속성이 너무 많을 때 일부 데이터만 가져오는 방법입니다. 즉, select 쿼리에서 일부 대상만 가져오는 것 입니다.
@Test
public void simpleProjection() {
List<String> result = queryFactory
.select(member.username)
.from(member)
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
프로젝션 대상이 둘 이상일 때 사용
com.querydsl.core.Typle
@Test
public void tupleProjection() {
List<Tuple> result = queryFactory
.select(member.username, member.age)
.from(member)
.fetch();
for (Tuple tuple : result) {
String username = tuple.get(member.username);
Integer age = tuple.get(member.age);
System.out.println("username = " + username);
System.out.println("age = " + age);
}
}
순수 JPA에서의 DTO 조회
@Data
@NoArgsConstructor
public class MemberDto {
private String username;
private int age;
}
@Test
public void findDtoByJPQL() {
List<MemberDto> result = em.createQuery("select new study.querydsl.dto.MemberDto(m.username, m.age) from Member m", MemberDto.class)
.getResultList();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
결과를 DTO로 반환할 때 사용하며, 다음 3가지 방법을 지원합니다.
- 프로퍼티 접근
- 필드 직접 접근
- 생성자 사용
프로퍼티 접근 - Setter
@Test
public void findDtoBySetter() {
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
필드 직접 접근
@Test
public void findDtoByField() {
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
@Data
public class UserDto {
private String name;
private int age;
}
@Test
public void findUserDto() {
QMember memberSub = new QMember("memberSub");
List<UserDto> result = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
ExpressionUtils.as(JPAExpressions
.select(memberSub.age.max())
.from(memberSub), "age")
))
.from(member)
.fetch();
for (UserDto userDto : result) {
System.out.println("userDto = " + userDto);
}
}
프로퍼티나 필드 접근 생성 방식에서 이름이 다를 때 해결 방안은
ExpressionUtils.as(source,alias)
: 필드나, 서브 쿼리에 별칭을 적용합니다.username.as("memberName")
: 필드에 별칭을 적용합니다.username.as()
방식이 간편하지만 서브쿼리를 사용하려면 전자의 방식을 사용해야합니다. @Test
public void findDtoByConstructor() {
List<UserDto> result = queryFactory
.select(Projections.constructor(UserDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (UserDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
@Data
public class MemberDto {
private String username;
private int age;
public MemberDto() {
}
@QueryProjection
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
gradle을 열어 compileQuerydsl을 두번 클릭해주면 QMemberDto
가 생성된 것을 확인할 수 있습니다.
@Test
public void findDtoByQueryProjection() {
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
이 방식은 컴파일러로 타입을 체크할 수 있어서 가장 안전하고 편한 방법이지만 DTO에 Querydsl 어노테이션을 유지해야 한다는 점에서 querydsl 라이브러리에 의존적인 단점이있꼬, DTO까지 Q파일을 생성해야 하는 단점이 있습니다.