QDSL - 기본 문법

2022. 12. 8. 09:42BackEnd(Java)/QueryDSL

이 글은 김영한님의 QueryDSL 강의를 듣고  정리한 글입니다.

 

 

where 조건을 사용한 특정 멤버 찾기(JPQL VS Querydsl )

@DisplayName("특정 멤버를 찾는 JPQL 예시")
@Test
public void startJPQL() {
    //given, then
    //member1을 찾아라
    Member findMember = em.createQuery("select m from Member m where m.username =:username", Member.class)
            .setParameter("username", "member1")
            .getSingleResult();

    //then
    assertThat(findMember.getUsername()).isEqualTo("member1");

}

 

@DisplayName("특정 멤버를 찾는 Qdsl 예시")
@Test
public void startQdsl() {
    /**
     * Q Type은 아래 코드 처럼 static import 하여 깔끔하게 사용하도록 하자
     */
    Member findMember = queryFactory.selectFrom(member)
            .where(member.username.eq("member1"))
            .fetchOne();

    assertThat(findMember.getUsername()).isEqualTo("member1");

}

 

 

where 조건을 사용한 특정 멤버 찾기

@DisplayName("where 필터 조건 and 사용")
@Test
public void search() {
    /**
     * member.username.eq("member1") // username = 'member1'
     * member.username.ne("member1") //username != 'member1'
     * member.username.eq("member1").not() // username != 'member1'
     * member.username.isNotNull() //이름이 is not null
     * member.age.in(10, 20) // age in (10,20)
     * member.age.notIn(10, 20) // age not in (10, 20)
     * member.age.between(10,30) //between 10, 30
     * member.age.goe(30) // age >= 30
     * member.age.gt(30) // age > 30
     * member.age.loe(30) // age <= 30
     * member.age.lt(30) // age < 30
     * member.username.like("member%") //like 검색
     * member.username.contains("member") // like ‘%member%’ 검색
     * member.username.startsWith("member") //like ‘member%’ 검색
     */

    //given
    //when
    Member findMember = queryFactory.selectFrom(member)
            .where(member.username.eq("member1").
                    and(member.age.eq(10))
            ).fetchOne();


    List<Member> memberList = queryFactory.selectFrom(member)
            .where(member.username.startsWith("member"))
            .fetch();

    memberList.forEach(System.out::println);

    //then
    assertThat(findMember.getUsername()).isEqualTo("member1");
    assertThat(findMember.getAge()).isEqualTo(10);
}

 

@DisplayName("where의 and를 ,(쉼표)를 사용하여 구분하는 방법")
@Test
public void searchAndParam() {
    //given
    //when
    Member findMember = queryFactory.selectFrom(member)
            .where(
                    member.username.eq("member1"),      //and를 안쓰고 ,(쉼표)로 구분이 가능함
                    member.age.eq(10)
            ).fetchOne();


    //then
    System.out.println("findMember = " + findMember);
}

 

 

결과 조회(fetch, fetchFrist, fetchResult 등...)

 @DisplayName("fetch")
    @Test
    public void resultFetch() {
        //fetch() : List로 조회
        List<Member> members = queryFactory.selectFrom(member)
                .fetch();

        //fetchOne() : 단건 조회
        Member fetchOne = queryFactory.selectFrom(member)
//                .where(member.username.eq("member1")) //단건 조회가 아니면  NonUniqueResultException: query did not return a unique result 예외 발생
                .fetchOne();

        Member fetchFirst = queryFactory.selectFrom(member)
                .fetchFirst();

        //페이징 + 데이터 => select 쿼리 + count 쿼리가 같이 나간다
        QueryResults<Member> results = queryFactory.selectFrom(member)
                .fetchResults();

        long total = results.getTotal();
        List<Member> results1 = results.getResults();
        long limit = results.getLimit();


        //count 쿼리
        long count = queryFactory.selectFrom(member)
                .fetchCount();


        /**
         * fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
         * fetchOne() : 단 건 조회,
         *  -> 결과가 없으면 : null
         *  -> 결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException
         * fetchFirst() : limit(1).fetchOne()
         * fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행
         * fetchCount() : count 쿼리로 변경해서 count 수 조회
         */

    }

 

 

정렬



/**
 * 회원 정렬 순서
 * 1. 회원 나이 내림차순
 * 2. 회원 이름 오름차순
 * 단 2에서 회원 이름이 없으면 마지막에 출력(nulls last)
 */
@DisplayName("정렬")
@Test
public void sort() {
    //given
    em.persist(new Member(null, 100));
    em.persist(new Member("member5", 100));
    em.persist(new Member("member6", 100));

    //when
    List<Member> members = queryFactory.selectFrom(member)
            .where(member.age.eq(100))
            .orderBy(member.age.desc(), member.username.asc().nullsLast())
            .fetch();

    members.forEach(System.out::println);

    //then
    assertThat(members.get(0).getUsername()).isEqualTo("member5");
    assertThat(members.get(1).getUsername()).isEqualTo("member6");
    assertThat(members.get(2).getUsername()).isNull();
}

 

 

페이징

@DisplayName("페이징 1")
@Test
public void paging1() {
    /**
     *  select
     *         member0_.member_id as member_i1_1_,
     *         member0_.age as age2_1_,
     *         member0_.team_id as team_id4_1_,
     *         member0_.username as username3_1_
     *     from
     *         member member0_
     *     order by
     *         member0_.username desc limit ? offset ?
     */
    List<Member> members = queryFactory
            .selectFrom(member)
            .orderBy(member.username.desc())
            .offset(1)
            .limit(2)
            .fetch();

    assertThat(members.size()).isEqualTo(2);
    /**
     * Select * from 테이블명 orders LIMIT 숫자(★) OFFSET 숫자(♥);
     *    LIMIT 숫자 : 출력할 행의 수
     *    OFFSET 숫자 : 몇번째 row부터 출력할 지. (1번째 row면 0)
     *
     * Ex) 10행씩 출력
     * 1페이지 : select * from member ORDERS LIMIT 10 OFFSET 0;
     * 2페이지 : select * from member ORDERS LIMIT 10 OFFSET 10;
     */

}

@DisplayName("페이징 2")
@Test
public void paging2() {
    //given
    //when
    QueryResults<Member> fetchResults = queryFactory
            .selectFrom(member)
            .orderBy(member.username.desc())
            .offset(1)
            .limit(3)
            .fetchResults();


    fetchResults.getResults()
            .forEach(System.out::println);

    //then
    assertThat(fetchResults.getTotal()).isEqualTo(4);
    assertThat(fetchResults.getLimit()).isEqualTo(3);
    assertThat(fetchResults.getOffset()).isEqualTo(1);
}

 

 

 

집합

@DisplayName("집합")
@Test
public void aggregation() {
    //given
    List<Tuple> result = queryFactory
            .select(
                    member.count(),
                    member.age.sum(),
                    member.age.avg()
            )
            .from(member)
            .fetch();


    //when
    Tuple tuple = result.get(0);
    Long count = tuple.get(member.count());
    Integer age = tuple.get(member.age.sum());
    Double avg = tuple.get(member.age.avg());


    /**
     *     select
     *         count(member0_.member_id) as col_0_0_,
     *         sum(member0_.age) as col_1_0_,
     *         avg(cast(member0_.age as double)) as col_2_0_
     *     from
     *         member member0_
     */


    //then
}

 

 

집합 - 그룹바이

/**
 * 팀의 이름과 각 팀의 평균 연령을 구해라
 */
@DisplayName("집합 - 그룹바이")
@Test
public void groupby() {
    //given
    List<Tuple> result = queryFactory
            .select(team.name, member.age.avg())
            .from(member)
            .join(member.team, team)
            .groupBy(team.name)
            .fetch();

    /**
     *  select
     *         team1_.name as col_0_0_,
     *         avg(cast(member0_.age as double)) as col_1_0_
     *     from
     *         member member0_
     *     inner join
     *         team team1_
     *             on member0_.team_id=team1_.id
     *     group by
     *         team1_.name
     */

    //when
    Tuple teamA = result.get(0);
    Tuple teamB = result.get(1);

    //then
    assertThat(teamA.get(team.name)).isEqualTo("TeamA");
    assertThat(teamA.get(member.age.avg())).isEqualTo(15);

    assertThat(teamB.get(team.name)).isEqualTo("TeamB");
    assertThat(teamB.get(member.age.avg())).isEqualTo(35);
}

 

 

조인

@DisplayName("팀 A에 소속된 모든 회원")
@Test
public void join() {
    //given
    List<Member> members = queryFactory
            .selectFrom(member)
            .join(member.team, team)
            .where(member.team.name.eq("TeamA"))
            .fetch();

    //when
    members.forEach(System.out::println);


    //then
    assertThat(members)
            .extracting("username")
            .containsExactly("member1", "member2");
}

 

세타 조인

@DisplayName("세타조인 - 회원의 이름이 팀 이름과 같은 회원 조회")
@Test
public void thetaJoin() {
    //given
    em.persist(new Member("teamA"));
    em.persist(new Member("teamB"));
    em.persist(new Member("teamC"));

    //when
    List<Member> result = queryFactory
            .select(member)
            .from(member, team)
            .where(member.username.eq(team.name))
            .fetch();

    /**
     *         select
     *             member0_.member_id as member_i1_1_,
     *             member0_.age as age2_1_,
     *             member0_.team_id as team_id4_1_,
     *             member0_.username as username3_1_
     *         from
     *             member member0_ cross
     *         join
     *             team team1_
     *         where
     *             member0_.username=team1_.name
     */

    //then
    assertThat(result)
            .extracting("username")
            .containsExactly("teamA", "teamB");
}

 

아우터 조인

/**
 * JPQL : select m, t from Member m left join m.team t on t.name ='teamA'
 */
@DisplayName("회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인 회원은 모두 조회")
@Test
public void join_on_filtering() {
    //given
    List<Tuple> result = queryFactory
            .select(member, team)       //select가 여러가지 타입을 리턴해서 tuple을 리턴함
            .from(member)
            .leftJoin(member.team, team).on(team.name.eq("teamA"))
            .fetch();

    //when
    result.forEach(System.out::println);


    /**
     * select
     *             member0_.member_id as member_i1_1_0_,
     *             team1_.id as id1_2_1_,
     *             member0_.age as age2_1_0_,
     *             member0_.team_id as team_id4_1_0_,
     *             member0_.username as username3_1_0_,
     *             team1_.name as name2_2_1_
     *         from
     *             member member0_
     *         left outer join
     *             team team1_
     *                 on member0_.team_id=team1_.id and (team1_.name='teamA')
     */

    //then
}

 

 

패치 조인(미사용, 사용)

 @DisplayName("fetch조인 미사용")
    @Test
    public void fetchJoinNo() {
        //given
        em.flush();
        em.clear();

        //when
        Member findMember = queryFactory
                .selectFrom(member)
                .where(member.username.eq("member1"))
                .fetchOne();

//        String teamName = findMember.getTeam().getName();
//        log.warn("teamName = {} ",teamName);

        boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());//로딩된 엔티티인지 확인
        log.warn("loaded = {} ", loaded);

        //then
        assertThat(findMember.getUsername()).isEqualTo("member1");
    }


    @DisplayName("fetch조인 사용")
    @Test
    public void fetchJoinUse() {
        //given
        em.flush();
        em.clear();

        //when
        Member findMember = queryFactory
                .selectFrom(member)
                .join(member.team, team).fetchJoin()
                .where(member.username.eq("member1"))
                .fetchOne();

        boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());//로딩된 엔티티인지 확인
        log.warn("loaded = {} ", loaded);

        /**
         * 패치 조인 쿼리
         *         select
         *             member0_.member_id as member_i1_1_0_,
         *             team1_.id as id1_2_1_,
         *             member0_.age as age2_1_0_,
         *             member0_.team_id as team_id4_1_0_,
         *             member0_.username as username3_1_0_,
         *             team1_.name as name2_2_1_
         *         from
         *             member member0_
         *         inner join
         *             team team1_
         *                 on member0_.team_id=team1_.id
         *         where
         *             member0_.username=?
         */


        //then
        assertThat(findMember.getUsername()).isEqualTo("member1");
        assertThat(loaded).isTrue();
    }

 

 

서브쿼리

@DisplayName("나이가 가장 많은 회원 조회")
@Test
public void subQuery() {
    //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();

    /**
     *         select
     *             member0_.member_id as member_i1_1_,
     *             member0_.age as age2_1_,
     *             member0_.team_id as team_id4_1_,
     *             member0_.username as username3_1_
     *         from
     *             member member0_
     *         where
     *             member0_.age=(
     *                 select
     *                     max(member1_.age)
     *                 from
     *                     member member1_
     *             )
     */


    //then
    assertThat(result).extracting("age").containsExactly(40);
}

 

 

@DisplayName("나이가 평균 이상인 회원")
@Test
public void subQuerGoe() {
    //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();


    /**
     *         select
     *             member0_.member_id as member_i1_1_,
     *             member0_.age as age2_1_,
     *             member0_.team_id as team_id4_1_,
     *             member0_.username as username3_1_
     *         from
     *             member member0_
     *         where
     *             member0_.age>=(
     *                 select
     *                     avg(cast(member1_.age as double))
     *                 from
     *                     member member1_
     *             )
     */


    assertThat(result).extracting("age").containsExactly(30, 40);
}

 

 

@DisplayName("in 쿼리 예시")
@Test
public void subQueryIn() {
    //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();


    /**
     *         select
     *             member0_.member_id as member_i1_1_,
     *             member0_.age as age2_1_,
     *             member0_.team_id as team_id4_1_,
     *             member0_.username as username3_1_
     *         from
     *             member member0_
     *         where
     *             member0_.age in (
     *                 select
     *                     member1_.age
     *                 from
     *                     member member1_
     *                 where
     *                     member1_.age>?
     *             )
     */


    assertThat(result).extracting("age").containsExactly(20, 30, 40);
}

 

 

JPQL 서브쿼리 이슈

/**
 * [이슈]
 * JPA - JPQL 서브쿼리의 한계점으로 from절 서브쿼리(인라인 뷰)는 지원하지 않는다. 당연히 QDSL도 지원안함
 * <p>
 * [해결 방안]
 * 1. 서브쿼리를 join으로 변경한다
 * 2. 쿼리를 2번 분리해서 실행한다
 * 3. nativeSQL을 사용한다.
 */
@DisplayName("스칼라 서브(select 절)쿼리")
@Test
public void selectSubQuery() {
    //given
    QMember memberSub = new QMember("memberSub");


    //when
    List<Tuple> result = queryFactory
            .select(member.username,
                    JPAExpressions
                            .select(memberSub.age.avg())
                            .from(memberSub))
            .from(member)
            .fetch();


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

    //then
    result.forEach(System.out::println);
}

 

 

 

CASE문 

@DisplayName("case문 예시")
@Test
public void basicCase() {
    //given
    List<String> result = queryFactory
            .select(member.age
                    .when(10).then("열살")
                    .when(20).then("스무살")
                    .otherwise("기타"))
            .from(member)
            .fetch();
    /**
     *         select
     *             case
     *                 when member0_.age=? then ?
     *                 when member0_.age=? then ?
     *                 else '기타'
     *             end as col_0_0_
     *         from
     *             member member0
     */


    //when
    result.forEach(System.out::println);


    //then
}

/**
 * DB에서는 raw 데이터를 필터링하고, 그룹핑하고, 필요하면 계산하지만 DB에 부하가 안되게 최소한의 쿼리를 작성한다.(DB 비용이 크닌깐)
 * WAS(Application)에서 갖고와서 해결해야한다!
 */
@DisplayName("복잡한 case문")
@Test
public void complexCase() {
    //given
    CaseBuilder caseBuilder = new CaseBuilder();

    List<String> result = queryFactory
            .select(caseBuilder
                    .when(member.age.between(0, 20)).then("0~20살")
                    .when(member.age.between(0, 20)).then("0~20살")
                    .otherwise("늙은이")
            ).from(member)
            .fetch();


    //when
    result.forEach(System.out::println);


    //then
}

참고자료

https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84/dashboard

반응형