오봉이와 함께하는 개발 블로그

Querydsl - 스프링 데이터 페이징 활용 1 (Querydsl 페이징 연동) 본문

BE/JPA

Querydsl - 스프링 데이터 페이징 활용 1 (Querydsl 페이징 연동)

오봉봉이 2022. 9. 20. 02:52
728x90

스프링 데이터 페이징 활용1 - Querydsl 페이징 연동

  • 스프링 데이터의 Page, Pageable을 활용해보자.
  • 전체 카운트를 한번에 조회하는 단순한 방법
  • 데이터 내용과 전체 카운트를 별도로 조회하는 방법

사용자 정의 인터페이스에 페이징 2가지 추가

public interface MemberRepositoryCustom {
    List<MemberTeamDto> search(MemberSearchCondition condition);

    Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable);
    Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable);
}

전체 카운트를 한번에 조회하는 단순한 방법

searchPageSimple(), fetchResults() 사용

@Override
public Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageablepageable) {
    QueryResults<MemberTeamDto> results = queryFactory
            .select(new QMemberTeamDto(
                    member.id,
                    member.username,
                    member.age,
                    team.id,
                    team.name
            ))
            .from(member)
            .leftJoin(member.team, team)
            .where(
                    usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe()))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetchResults();

    List<MemberTeamDto> content = results.getResults();

    long total = results.getTotal();

    return new PageImpl<>(content, pageable, total);
}

.offset() : 몇 번째부터 시작할지
.limit() : 한 번 조회할 때 몇 개를 조회할지(한 페이지에 데이터 몇 개를 조회할지)
getResults() : 쿼리 결과에서 실제 컨텐츠를 DTO에 넣어주는 메소드

Querydsl이 제공하는 fetchResults()를 사용하면 내용과 전체 카운트를 한번에 조회할 수 있다.(실제 쿼리는 2번 호출 - 카운트 쿼리, 페이징 쿼리)
fetchResult()는 카운트 쿼리 실행시 필요없는 order by는 제거한다.

테스트

@Test
void searchPageSimple() {
    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);

    MemberSearchCondition condition = new MemberSearchCondition();
    PageRequest page = PageRequest.of(0, 3); // page 인덱스(0번부터 가져온다는 뜻), size(한페이지에 컨텐츠가 몇 개가 담기냐는 뜻)

    Page<MemberTeamDto> result = memberRepository.searchPageSimple(condition, page);

    assertThat(result.getSize()).isEqualTo(3);
    assertThat(result.getContent()).extracting("username").containsExactly("member1","member2", "member3");
}
-- 카운트 쿼리
select
    count(member0_.member_id) as col_0_0_
from
    member member0_
left outer join
    team team1_
        on member0_.team_id=team1_.team_id


-- 페이징 쿼리
select
    member0_.member_id as col_0_0_,
    member0_.username as col_1_0_,
    member0_.age as col_2_0_,
    team1_.team_id as col_3_0_,
    team1_.name as col_4_0_
from
    member member0_
left outer join
    team team1_
        on member0_.team_id=team1_.team_id limit ?

데이터 내용과 전체 카운트를 별도로 조회하는 방법

직접 total count 쿼리를 날리는 방법이다.

상황에 따라 다르지만, 예를 들어 count가 테이블에 데이터로 있다거나, join을 좀 줄여서 해도 count값에 변함이 없다거나 하는 등 count 쿼리를 줄일 수 있을 때가 있다.

전체 카운트를 조회하는 방법을 최적화 할 수 있으면 이렇게 분리해서 사용하자.
성능 최적화에 상당한 효과를 얻을 수 있을 것이다.

또, 코드를 리펙토링해서 내용 쿼리과 전체 카운트 쿼리를 읽기 좋게 분리하면 좋다.(메소드화 시킨다.)

searchPageComplex()

/**
 * 복잡한 페이징
 * 데이터 조회 쿼리와, 전체 카운트 쿼리를 분리
 **/
@Override
public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageablepageable) {
    // content만 가져옴.
    List<MemberTeamDto> content = queryFactory
            .select(new QMemberTeamDto(
                    member.id,
                    member.username,
                    member.age,
                    team.id,
                    team.name
            ))
            .from(member)
            .leftJoin(member.team, team)
            .where(
                    usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe()))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetch();

    // total count 쿼리
    long total = queryFactory
            .select(member)
            .from(member)
            .leftJoin(member.team, team)
            .where(
                    usernameEq(condition.getUsername()),
                    teamNameEq(condition.getTeamName()),
                    ageGoe(condition.getAgeGoe()),
                    ageLoe(condition.getAgeLoe()))
            .fetchCount();

    return new PageImpl<>(content, pageable, total);
}
인프런 김영한 지식공유자님 강의 : 실전! Querydsl
728x90
Comments