본문 바로가기
Spring/Querydsl

QueryDsl 프로젝션 문법

코동이 2022. 2. 13.

프로젝션


프로젝션이란 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