JPA

QueryDSL 정렬과 페이징 처리 및 집합 예시

빈코 2022. 10. 31. 19:45

이전 포스팅에서는 Query DSL을 사용하여 데이터를 조회하는 간단한 예시를 만들었었습니다. 이번 포스팅에서는 조회한 결과물을 정렬하고 페이징 처리하는 방법에 대해 알아볼게요😄

 

로고
Query DSL

 

개요

관계도
예시 관계도

이전 포스팅에서 사용했던 엔티티들을 재사용 할 예정입니다. 혹여 엔티티 클래스의 코드가 궁금하신 분들은 엔티티 설계 포스팅을 참고해주세요😄

 

📌 저는 BeforeEach 어노테이션을 활용해 가데이터가 들어간 상황에서 테스트를 진행한 것입니다.

 

@BeforeEach
public void before() {
    queryFactory = new JPAQueryFactory(em);
    Team teamA = new Team("teamA");
    Team teamB = new Team("teamB");
    em.persist(teamA);
    em.persist(teamB);

    Member member1 = new Member("member1", 10, teamA);
    Member member2 = new Member("member2", 20, teamA);

    Member member3 = new Member("member3", 30, teamB);
    Member member4 = new Member("member4", 40, teamB);
    em.persist(member1);
    em.persist(member2);
    em.persist(member3);
    em.persist(member4);
}
MEMBER_ID NAME AGE TEAM_NAME
1 member1 10 teamA
2 member2 20 teamA
3 member3 30 teamB
4 member4 40 teamB

 

정렬(Sort)📒

정렬하는 방법은 크게 2가지로 나뉩니다. 일반 정렬과 null 데이터 순서 정렬인데, 흔히 아는 desc(), asc()로 내림차순, 오름차순으로 정렬할 수 있으며, nullsLast(), nullsFirst() 메서드를 이용하여 null 데이터의 순서도 부여할 수 있습니다.

 

간단한 예제를 볼까요?

 

    @Test
    public void sort() {
        em.persist(new Member(null, 100));
        em.persist(new Member("member5", 100));
        em.persist(new Member("member6", 100));

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

        Member member5 = result.get(0);
        Member member6 = result.get(1);
        Member memberNull = result.get(2);

        assertThat(member5.getUsername()).isEqualTo("member5");
        assertThat(member6.getUsername()).isEqualTo("member6");
        assertThat(memberNull.getUsername()).isNull();
    }

 

테스트 코드를 보면 첫 번째로 가데이터 회원 3명을 넣었습니다. 그리고 Query DSL을 사용하여 나이가 100살인 회원들 중에 나이 필드를 이용하여 내림차순으로 하고, 나이가 같다면 이름으로 오름차순으로 정렬하였습니다.

 

그리고 nullsLast()를 이용하여 이름이 없는 회원을 맨 마지막에 출력하게 하였습니다.

 

예상 값을 member 엔티티로 반환 받고 assertThat으로 확인한 결과 조건에 맞게 데이터들이 정렬되어 나오는 것을 확인할 수 있었습니다😀

 

테스트 결과
테스트 결과

테스트는 성공하였고, DB에 전달 된 SQL을 보면 원하는 조건들은 모두 들어간 걸 확인할 수 있었습니다.

 

페이징(Paging)📗

@Test
public void paging() {
    List<Member> result = queryFactory
            .selectFrom(member)
            .orderBy(member.username.desc())
            .offset(1)
            .limit(2)
            .fetch();

    assertThat(result.size()).isEqualTo(2);
}

 

페이징 처리 또한 간단하게 구현이 가능하다. offset을 통해 시작 index를 알리고, limit을 통해 가져올 결과물 수를 제한하면 된다. 사실 실무에서는 이렇게 데이터만 가져오는 페이징은 잘 사용하지 않고, 데이터의 총개수를 찾아서 이전/다음 페이지 유무, 총 페이지 수 등을 구해야 한다.

 

Query DSL 초기 버전에서는 위에서 사용한 fetch() 대신 fetchResults()를 이용하여 카운트 쿼리를 동시에 날려서 데이터의 총 갯수(원하는 페이지 X, where 조건 문에 합당하는 모든 데이터 수)를 가져올 수 있었다.

 

하지만 Query DSL 5.0 버전에서 fetchResults()의 사용을 추천하지 않는 다는 내용이 추가되었다. 그 이유는 count 쿼리가 모든 dialect에서 또는 다중 그룹 쿼리에서 완벽하게 지원되지 않기 때문이다.

 

그래서 대부분 카운트 쿼리가 필요할 때는 따로 쿼리를 작성하곤 한다😀

 

@Test
public void paging() {
    int totalSize = queryFactory
            .selectFrom(member)
            .fetch().size();

    assertThat(totalSize)).isEqualTo(2);
}

 

집합(Aggregation)📔

@Test
public void aggregation() {
    List<Tuple> result = queryFactory
            .select(member.count(),
                    member.age.sum(),
                    member.age.avg(),
                    member.age.max(),
                    member.age.min()
            )
            .from(member)
            .fetch();

    Tuple tuple = result.get(0);
    assertThat(tuple.get(member.count())).isEqualTo(4);
    assertThat(tuple.get(member.age.sum())).isEqualTo(100);
    assertThat(tuple.get(member.age.max())).isEqualTo(40);
    assertThat(tuple.get(member.age.min())).isEqualTo(10);
}

 

집합 함수 또한 JPQL이 제공하는 모든 집합 함수를 제공한다. 위 코드와 같이 총 갯수를 구하는 count(), 합계 sum(), 평균 avg(), 최대값 max(), 최소값 min() 등 다양하게 지원한다.

 

GroupBy 사용📘

@Test
public void group() {
    List<Tuple> result = queryFactory
            .select(team.name, member.age.avg())
            .from(member)
            .join(member.team, team)
            .groupBy(team.name)
            .fetch();

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

    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);
}

 

select 절을 이용하여 해당 팀의 이름과 팀 안에 소속된 멤버들의 평균 나이를 구하고, groupBy를 사용하여 teamA와 teamB로 결과값을 받았습니다. 

 

DB에 전달된 쿼리문을 살펴보면 원하는 요구사항에 맞게 조인과 group by가 조건문으로 들어가는 것을 확인할 수 있습니다.

 

📌그룹화된 결과를 제한하려면 having절을 사용해야 합니다. 

 

 


👨‍👩‍👦‍👦 오픈채팅방 운영

취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁

 

참여코드 : 456456

https://open.kakao.com/o/gVHZP8dg

 

비전공 개발자 취업 준비방(질문&답변)

#비전공 #개발자 #취업 #멘토링 #부트캠프 #국비지원 #백엔드 #프론트엔드 #중소기업 #중견기업 #자바 #Java #sql

open.kakao.com

 


👨‍💻 전자책 출간

아울러 제가  🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁

https://kmong.com/gig/480954

 

비전공개발자 2년만에 중견기업 들어간 방법 | 14000원부터 시작 가능한 총 평점 0점의 전자책, 취

0개 총 작업 개수 완료한 총 평점 0점인 Binco의 전자책, 취업·이직 전자책 서비스를 0개의 리뷰와 함께 확인해 보세요. 전자책, 취업·이직 전자책 제공 등 14000원부터 시작 가능한 서비스

kmong.com


 

 

마치며

지금까지 Query DSL을 사용하여 정렬하는 방법, 페이징 처리, 집합 관련 로직들을 간단한 예시를 통해 알아보았습니다. 다음 포스팅은 Query DSL을 이용하여 조인하는 방법에 대해 알아볼게요😄

 

QueryDSL Series

1. QueryDSL 적용 및 예제 시리즈 시작

2. QueryDSL 조회 예제

3. QueryDSL 정렬 & 페이징 & 집합(현재)

4. QueryDSL 조인 예제

5. QueryDSL Projection

6. QueryDSL 동적 쿼리 & Bulk 연산

7. QueryDSL 실무 활용

 

Reference

실전! Querydsl

 

반응형