Querydsl - 서브 쿼리
인프런 실전! Querydsl 강의 내용 정리
com.querydsl.jpa.JPAExpressions 사용
코드로 직접 보자. 나이가 가장 많은 회원 조회를 해보자.
우린 where 절에서 서브 쿼리로 max 회원을 가져온 다음에 조건으로 사용할 것이다.
그럼 먼저 SQL이면 어떻게 할까?
1
2
3
4
5
6
SELECT *
FROM MEMBER M
WHERE M.AGE = (
SELECT MAX(AGE)
FROM MEMBER
)
이렇게 짤 거 같다.
자 이제 JPAExpressions을 사용해서 querydsl에서 서브 쿼리를 어떻게 사용하는지 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*나이가 가장 많은 회원 조회*/
@Test
public void subQuery() throws Exception {
//given
QMember memberSub = new QMember("memberSub");
//when
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(
JPAExpressions
.select(memberSub.age.max())
.from(memberSub)
))
.fetch();
//then
assertThat(result).extracting("age")
.containsExactly(40);
}
나름 직관적이지 않은가?
QMember memberSub = new QMember("memberSub");
먼저 이렇게 다른 쿼리(?)로 동작 할 수 있게 QMember 객체를 하나 더 만들어 준다.
그 다음에 이걸 서브 쿼리에서 사용할 것이다. 이 부분만 주의하면 된다.
물론 JPAExpressions 이게 어색하긴 하지만
1
2
3
4
.where(member.age.eq(
JPAExpressions
.select(memberSub.age.max())
.from(memberSub)
where 절 → 멤버의 나이가 같은가? → 서브 쿼리를 사용하기 위한 객체 →
서브 쿼리 → 멤버 테이블에서 가장 나이가 큰 값은 얼마인가?
흐름이 SQL이랑 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
where
m1_0.age=(
select
max(m2_0.age)
from
member m2_0
)
로그에 쿼리도 내가 생각한 거랑 동일하게 나갔다.
이제 여러 문제를 풀어보자.
1. 나이가 평균 이상인 회원
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**/
@Test
public void subQueryGoe() throws Exception {
//given
QMember memberSub = new QMember("memberSub");
//when
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.goe(
JPAExpressions
.select(memberSub.age.avg())
.from(memberSub)
))
.fetch();
//then
assertThat(result).extracting("age")
.containsExactly(30,40);
}
where 절에서 goe()를 사용해서 >= 를 표현 했고, 서브 쿼리에선 avg()를 사용해서 평균을 가져왔다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
select
m1_0.member_id,
m1_0.age,
m1_0.team_id,
m1_0.username
from
member m1_0
where
m1_0.age>=(
select
avg(cast(m2_0.age as float(53)))
from
member m2_0
)
쿼리는 다음과 같이 나간다.
2. in절 10살 초과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void subQueryIn() throws Exception {
//given
QMember memberSub = new QMember("memberSub");
//when
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.in(
JPAExpressions
.select(memberSub.age)
.from(memberSub)
.where(memberSub.age.gt(10))
))
.fetch();
//then
assertThat(result).extracting("age")
.containsExactly(20, 30,40);
}
in도 다음과 같이 사용하면 된다.
3. select 절에서 서브 쿼리 사용하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void selectSubQuery() throws Exception {
//given
QMember memberSub = new QMember("memberSub");
//when
List<Tuple> fetch = queryFactory
.select(member.username,
JPAExpressions
.select(memberSub.age.avg())
.from(memberSub)
)
.from(member)
.fetch();
//then
for (Tuple tuple : fetch) {
System.out.println("tuple = " + tuple);
}
}
select 절에서 당연히 위에서 사용한 것처럼 사용할 수 있다.

다음과 같이 결과도 잘 나왔다.
from 절의 서브 쿼리 한계
JPA JPQL 서브 쿼리의 한계 점으로 from 절의 서브 쿼리(인라인 뷰)는 지원하지 않는다.
당연히 QueryDsl도 지원하지 않는다.
하이버네이트 구현체를 사용하면 select절의 서브 쿼리는 지원 한다.
QueryDsl도 하이버네이트 구현체를 사용하면 select절의 서브 쿼리를 지원한다.
Hibernate 6.1 이상부터 from절에
서브 쿼리를 지원 한다고 한다.그런데 아직 까진 queryDsl에선 지원 하지 않는 것 같다. queryDsl issue
from 절의 서브 쿼리 해결 방안
- 서브 쿼리를 join으로 변경한다. (가능한 상황도 있고, 불가능한 상황도 있다.)
- 애플리케이션 에서 쿼리를 2번 분리해서 실행한다.
- nativeSQL을 사용한다…..
댓글남기기