프로젝션
프로젝션이란 select 대상 지정을 의미합니다. 대상이 1개인지 복수개인지에 따라 방식이 달라집니다
셀렉션(selection)은 행 단위의 조회입니다
프로젝션(projection)은 열 단위의 조회입니다. 따라서 프로젝션 조회란, 행이 아닌 열을 중심으로 생각해야 합니다.
1. 프로젝션 대상이 하나
단순히 username만 조회하기 때문에 일반 조회처럼 하면 됩니다.
List<String> result = queryFactory
.select(member.username)
.from(member)
.fetch();
2. 튜플 조회
프로젝트 대상이 둘 이상일 때는 튜플 조회를 사용합니다.
@Test
public void tupleQuery() {
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);
}
}
Tuple은 여러 타입을 조회할 때 사용하기 위해서 QueryDsl에서 만든 것입니다.
import를 살펴보면 QueryDsl에서 만든 것임을 알 수 있습니다. username과 age 2개 이상의 프로젝션을 조회하기 떄문에 Tuple을 사용합니다.
운영에서 Tuple은 레포지토리 계층에서 사용하고, 서비스 계층에서는 사용하지 않도록 합니다. 의존성을 최대한 줄여야 Querydsl에서 다른 기술을 바꾸더라도 쉽게 바꿀 수 있습니다. Tuple이 Querydsl에 종속적이므로 dto로 전환시켜서 던져야 합니다.
프로젝션 결과 반환 dto
1. 순수 JPA에서 DTO 조회 코드
DTO를 조회할 때는 new 명령어를 사용해야 합니다. 그런데, package를 다 적어주어야 하므로 지저분합니다. MemberDto는 무조건 생성자가 있어야 합니다.
List<MemberDto> result = em.createQuery(
"select new study.querydsl.dto.MemberDto(m.username, m.age) " +
"from Member m", MemberDto.class)
.getResultList();
Member에서 조회한 값들을 MemberDto에 담아서 반환합니다.
2. Querydsl 빈 생성(Bean population)
결과를 Dto를 반환할 때는 3가지 방식을 지원합니다.
- 프로퍼티 접근 : Setter를 통해서 설정
- 필드직접 접근 : dto의 필드에 직접 설정
- 생성자 적용 : 모든 매개변수가 있는 생성자에 직접 설정
1. 프로퍼티 접근
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
혹시 아래와 같은 오류가 뜬다면 기본 생성자가 없다는 것입니다. 기본생성자 혹은 @NoArgsConstructor를 추가합니다.
JPQL도 username, age를 가져오고 쿼리도 마찬가지입니다.
2. 필드 직접 접근
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
Setter방식과 마찬가지로 결과가 나타납니다.
만약에, UserDto에서 username 대신에 name이라는 필드를 사용하고 있다면 어떻게 쿼리문을 개선할까? 별칭을 사용해서 변수명을 정해주어야 합니다. ExpressionUtils.as(source, alias)로 서브쿼리에 별칭을 줄 수 있습니다.
List<UserDto> fetch = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
ExpressionUtils.as(
JPAExpressions
.select(memberSub.age.max())
.from(memberSub), "age")
)
).from(member)
.fetch();
3. 생성자 접근
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
}
Setter와 dto와 마찬가지로 생성자 설정도 다수의 프로젝션을 조회합니다.
Projection 결과 반환 - DTO 조회(@QueryProjection)
MemberDto 클래스에서 매개변수가 있는 생성자에 @QueryProjection을 추가합니다
Gradle - other - compileQuerydsl을 누릅니다. 그러면 MemberDto도 QMemberDto 파일이 생성됩니다.
QMemberDto파일에는 생성자가 있는데, 해당 내용을 이용합니다.
@Generated("com.querydsl.codegen.ProjectionSerializer")
public class QMemberDto extends ConstructorExpression<MemberDto> {
private static final long serialVersionUID = -857882546L;
public QMemberDto(com.querydsl.core.types.Expression<String> username, com.querydsl.core.types.Expression<Integer> age) {
super(MemberDto.class, new Class<?>[]{String.class, int.class}, username, age);
}
}
생성자를 이용하면 쿼리를 보다 간편하게 짤 수 있습니다.
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
그 결과 아래와 같이 JPQL과 SQL 모두 쿼리가 잘 반영됩니다.
DTO 조회방법 중, Projections.constructor 생성자 방식은 런타임 오류에서 잡습니다.
하지만, @QueryProjection은 컴파일에 오류를 잡습니다.
따라서 생성자를 이용한다는 점에서는 같지만 @QueryPorjection은 오류를 훨씬 줄일 수 있습니다.
- 단점
1, Q파일을 생성해야 해야합니다.
2. @QueryProjection을 dto파일에 사용하기 때문에 Querydsl 의존성이 추가됩니다.
(Controller, Service 등에서 조회된 내용들이 사용되어 프로젝트 전체적으로 @QueryProjection에 의존합니다)
dinstinct
select() 뒤에 distinct()를 붙이면 됩니다.
List<String> result = queryFactory
.select(member.username).distinct()
.from(member)
.fetch();
* 출처
Inflearn : 실전! Querydsl 강의
'Spring > Querydsl' 카테고리의 다른 글
순수 JPA와 Querydsl (0) | 2022.02.13 |
---|---|
QueryDsl 수정, 삭제 (0) | 2022.02.13 |
Querydsl 동적쿼리 (0) | 2022.02.13 |
QueryDsl 기본문법 (0) | 2022.02.12 |
JPA & JPQL & QueryDsl (0) | 2022.02.12 |