[JPA] Querydsl DTO로 조회하기
group by, order by 등을 써야해서 querydsl을 사용했다.
일단 아래의 코드를 통해 원하는 값을 조회하는 것은 성공
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class TopContentsDTO {
private int likes;
private Contents contents;
}
@Test
void selectBestContents() {
QAssessment qA = QAssessment.assessment;
List<Tuple> result = jpaQF
.select(qA.likes.count(), qA.contents.id, qA.contents.contentsName)
.from(qA)
.where(qA.likes.eq(1))
.groupBy(qA.contents.id)
.orderBy(qA.likes.count().desc())
.limit(3)
.fetch();
for(int i = 0; i < result.size(); i++) {
System.out.println("----- " + result.get(i));
}
}
그런데
여기서 각각의 값을 어떻게 가져올지를 모르겠어서 방법을 찾아보니 딱히 끌리지 않았다.
'이런 방법을 사람들이 그대로 사용할리가 없다 분명 더 좋은 방법이 있을 것이다' 라는 확신...
그리고 List<>안에 타입을 DTO로 하고싶기도 했다.
그래서 찾아본 방법
- Projections (bean, fields, constructor)
- @QueryProjection
Projections
컴파일 시에는 에러를 잡을 수 없다. 런타임 시에만 에러를 잡을 수 있다.
Projections.bean / Projections.fields
.bean은 setter를 기반으로 동작한다.
.fields는 필드에 직접 값을 넣으므로 setter는 필요 없다.
bean과 fields은 필드 이름이 다를 경우 값을 가져오지 못한다.
해결책으로는 .as("DTO 변수 이름"); 을 사용할 수 있다.
Projections.constructor
생성자 기반으로 바인딩해준다.
→ DTO 객체를 불변으로 가질 수 있다.
→ 필드 이름이 달라도 상관 없다.
→ 조회 값과 DTO 변수 타입이 일치해야 한다.
→ 값을 넘길 때 생성자와 순서를 일치시켜줘야하므로, 값이 많아질 경우 문제가 생길 수 있다.
@Test
void selectBestContents() {
QAssessment qA = QAssessment.assessment;
List<TopContentsDTO> result = jpaQF
.select(Projections.constructor(TopContentsDTO.class, qA.likes.count().intValue(), qA.contents))
.from(qA)
.where(qA.likes.eq(1))
.groupBy(qA.contents.id)
.orderBy(qA.likes.count().desc())
.limit(3)
.fetch();
}
@QueryProjection
불변 객체 선언 및 생성자를 유지할 수 있다.
컴파일 시 에러를 잡을 수 있다.
DTO 특성 상 많은 계층에서 사용하게 되는데, qureydsl의 의존성이 필요 없는 경우에도 해당 의존성이 필요하게 된다.
@Getter
@NoArgsConstructor
public class TopContentsDTO {
private int likes;
private Contents contents;
@QueryProjection
public TopContentsDTO(int likes, Contents contents) {
this.likes = likes;
this.contents = contents;
}
}
@Test
void selectBestContents() {
QAssessment qA = QAssessment.assessment;
List<TopContentsDTO> result = jpaQF
.select(new QTopContentsDTO(qA.likes.count().intValue().as("likes"), qA.contents))
.from(qA)
.where(qA.likes.eq(1))
.groupBy(qA.contents.id)
.orderBy(qA.likes.count().desc())
.limit(3)
.fetch();
for(int i = 0; i < result.size(); i++) {
int likes = result.get(i).getLikes();
Contents contents = result.get(i).getContents();
Long contentsId = contents.getId();
String contentsName = contents.getContentsName();
System.out.println("-----" + likes + " " +contentsId + " " + contentsName); //
}
}
Projections.constructor 과 @QueryProjection 중에 고민했는데
@QueryProjection을 사용해보고자 한다.
필요없는 의존성이 생긴다는 부분이 걸려서 괜찮은가 싶지만
개인적으로는 아래와 같이 컴파일 시 에러를 잡을 수 있다는 점이 매력적이라서;
Projections.constructor 사용 시에는 qA.liks.count() 까지만 해줘도 에러표시가 안 났는데
@QueryProjection 방식으로 하니 바로 이렇게..!

참고
qA.likes.count().intValue()
- qA.likes.count() : sql로 보자면 count(likes)
- .intValue() : count()가 Long 타입을 반환하므로 int로 처리해줌
intValue()를 해주기 전에는 java.lang.long is not compatible with int 이라는 에러가 발생했다.
참고
https://doing7.tistory.com/129
[Querydsl] 튜플이나 DTO로 결과 반환하기
프로젝션 : select 대상지정하는 일 프로젝션 대상이 두개 이상이라면 튜플이나 DTO로 조회해야한다. 🌱 튜플 사용하기 com.querydsl.core.Tuple를 사용하고 있다. 때문에 Repository 계층을 넘어서 Service나
doing7.tistory.com
https://wildeveloperetrain.tistory.com/94
Querydsl DTO 조회하는 방법(Projection, @QueryProjection)
Projection 연산이란, - 한 Relation의 Attribute들의 부분 집합을 구성하는 연산자입니다. - 결과로 생성되는 Relation은 스키마에 명시된 Attribute들만 가집니다. - 결과 Relation은 기본 키가 아닌 Attribute..
wildeveloperetrain.tistory.com