스프링/QueryDsl

[QueryDsl] 서브쿼리 사용하기

nomoreFt 2022. 3. 10. 18:24

앞서서, 서브쿼리 사용시 생각해야할 점

현재 from절에서 서브쿼리가 불가능하다.

원인

JPA JPQL에서 from 절의 서브쿼리를 지원하지 않기 때문에, JPQL 기반의 QueryDsl도 지원되지 않는다.

하이버네이트 구현체를 사용하면(JPAExpressions) select절의 서비 쿼리는 지원한다.

from절의 서브쿼리 해결 방안 3가지로는

  1. 서브쿼리를 join으로 변경 시도
  1. app에서 쿼리를 분리해서 2번 실행하여 거름
  1. nativeSQL을 사용하기

💢그러나 from절에서 서브쿼리를 사용하는 많은 이유 두 가지는

  1. 화면에 완전 Fit하게 가져오기 위해
  1. 성능상의 이유로 단 한번의 query만 날려 가져오게 하기 위해

정도로 구분할 수 있는데, 과연 DB에서 순수하게 Data를 가져오는 역할을 시켜서 재사용성을 높히는 설계와,

걸러내는 로직을 Server에서 한다 를 포기할 만한 가치가 있는가 생각해보자.

또한, from 서브쿼리로 하나에 1000줄 짤거, sql을 두 세번 날리면 각각 100줄정도로 나눌 수 있는데

그렇게까지 쿼리 한두번이 아쉬울 정도의 고성능을 요구하려면 이미 cache나 다른 조치를 취해야 하는게 맞다.

JPAExpressions를 사용해 서브쿼리 제작하기

  1. where절에 서브쿼리예시 1. where 절 innerjoin으로 나이 가장 많은 회원 조회하기
  2. 💫주의사항 : 서브쿼리용 Entity는 alias가 달라야하기 때문에 따로 QMember 생성해준다.
    JPAExpressions를 static import로 빼면 코드가 더 간결해진다.
 @Test
    public void subQuery() throws Exception {
        *****
        //Member InnerJoin을 위해 alias를 새로 선언해서 QMember 생성해주는 모습
        *****
        QMember subMember = new QMember("subMember");
        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.eq(
                        JPAExpressions
                                .select(subMember.age.max())
                                .from(subMember)
                )).fetch();

        assertThat(result).extracting("age");

          /* select
        member1 
    from
        Member member1 
    where
        member1.age = (
            select
                max(subMember.age) 
            from
                Member subMember
        ) */ 

        /*select
        member0_.user_id as user_id1_0_,
                member0_.age as age2_0_,
        member0_.team_id as team_id4_0_,
                member0_.username as username3_0_
        from
        member member0_
        where
        member0_.age=(
                select
        max(member1_.age)
        from
        member member1_
            )*/
    }

예시 2. 나이 평균이상 멤버만 구하기

 @Test
    public void subQuery_avg() throws Exception{
        //서브쿼리용 Entity는 alias가 달라야하기 때문에 따로 QMember 생성해준다.
        QMember subMember=new QMember("subMember");
        List<Member> result=queryFactory
        .selectFrom(member)
        .where(member.age.goe(
        JPAExpressions
        .select(subMember.age.avg())
        .from(subMember)
        )).fetch();
        }

예시 3. 💫유용한 서브쿼리로 & in으로 조회

@Test
    public void subQuery_in() throws Exception {
        //서브쿼리용 Entity는 alias가 달라야하기 때문에 따로 QMember 생성해준다.
        QMember subMember = new QMember("subMember");
        List<Member> result = queryFactory
                .selectFrom(member)
                .where(member.age.in(
                        JPAExpressions
                                .select(subMember.age)
                                .from(subMember)
                                .where(subMember.age.gt(10))
                )).fetch();

        assertThat(result).extracting("age");
 /* select
        member1
    from
        Member member1
    where
        member1.age in (
            select
                subMember.age
            from
                Member subMember
            where
                subMember.age > ?1
        ) */
        /*select
        member0_.user_id as user_id1_0_,
                member0_.age as age2_0_,
        member0_.team_id as team_id4_0_,
                member0_.username as username3_0_
        from
        member member0_
        where
        member0_.age in (
                select
        member1_.age
                from
        member member1_
        where
        member1_.age>?
            )*/
    }

2.select에 서브쿼리

간단 예시 : 유저 이름과 평균 나이 함께 출력하기

@Test
    public void subQuery_select() throws Exception {
        QMember subMember = new QMember("subMember");
        List<Tuple> result = queryFactory
                .select(member.username,
                        JPAExpressions
                                .select(subMember.age.avg())
                                .from(subMember))
                .from(member)
                .fetch();

        for (Tuple tuple : result) {
            System.out.println(tuple);
        }

         /* select
        member1.username,
        (select
            avg(subMember.age)
        from
            Member subMember)
    from
        Member member1 */
        /*select
        member0_.username as col_0_0_,
                (select
        avg(cast(member1_.age as double))
        from
        member member1_) as col_1_0_
        from
        member member0_*/
    }