3 분 소요

인프런 실전! Querydsl 강의 내용 정리

CountQuery 최적화

CountQuery를 최적화 하는 방법에 대해 알아보자.

  • 스프링 데이터 라이브러리가 제공
  • count 쿼리가 생략 가능한 경우 생략해서 처리
    • 페이지 시작이면서 컨텐츠 사이즈가 페이지 사이즈 보다 작을 때
    • 마지막 페이지 일 때 (offset + 컨텐츠 사이즈를 더해서 전체 사이즈 구함)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Override  
public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable) {
	List<MemberTeamDto> contents = queryFactory  
	        .select(new QMemberTeamDto(  
	                member.id.as("memberId"),  
	                member.username,  
	                member.age,  
	                team.id.as("teamId"),  
	                team.name.as("teamName")  
	        ))  
	        .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();
                
	Long total = queryFactory  
	        .select(member.count())  
	        .from(member)  
	        .leftJoin(member.team, team)  
	        .where(  
	                usernameEq(condition.getUsername()),  
	                teamNameEq(condition.getTeamName()),  
	                ageGoe(condition.getAgeGoe()),  
	                ageLoe(condition.getAgeLoe())  
	        )  
	        .fetchOne();
}    

어제 다음과 같은 페이징 메서드를 만들었다.

이제 스프링이 지원하는 페이징 카운트 최적화 방법을 알아보자.

먼저 카운트 쿼리에 .fetchOne() 을 빼고 다시 변수를 추출 한다.

1
2
3
4
5
6
7
8
9
10
JPAQuery<Long> countQuery = queryFactory  
        .select(member.count())  
        .from(member)  
        .leftJoin(member.team, team)  
        .where(  
                usernameEq(condition.getUsername()),  
                teamNameEq(condition.getTeamName()),  
                ageGoe(condition.getAgeGoe()),  
                ageLoe(condition.getAgeLoe())  
        );

그 후 메서드의 리턴을 PageableExecutionUtils.getPage(contents, pagable, 람다식)

이런 형태로 리턴 해주면 된다. 그럼 전체 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Override  
public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable) {  
    List<MemberTeamDto> contents = queryFactory  
            .select(new QMemberTeamDto(  
                    member.id.as("memberId"),  
                    member.username,  
                    member.age,  
                    team.id.as("teamId"),  
                    team.name.as("teamName")  
            ))  
            .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();
            
    JPAQuery<Long> countQuery = queryFactory  
            .select(member.count())  
            .from(member)  
            .leftJoin(member.team, team)  
            .where(  
                    usernameEq(condition.getUsername()),  
                    teamNameEq(condition.getTeamName()),  
                    ageGoe(condition.getAgeGoe()),  
                    ageLoe(condition.getAgeLoe())  
            );  
  
    return PageableExecutionUtils.getPage(contents, pageable, countQuery::fetchOne);  
    //return new PageImpl<>(contents, pageable, total);  
}    

이렇게 하면 만약 조건이 성립하면 카운트 쿼리가 안 나가게 된다.

테스트로 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Test  
public void searchPageTest() throws Exception {  
    //given  
    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 pageRequest = PageRequest.of(0, 10);  
  
    //when  
    Page<MemberTeamDto> result = memberRepository.searchPageComplex(condition, pageRequest);  
    //then  
    assertThat(result.getContent()).extracting("username").containsExactly("member1", "member2", "member3", "member4");  
}

다음과 같이 페이지가 0부터 10까지 인데 데이터가 4건이면

테스트를 돌려보면

딱 컨텐츠를 가져오는 쿼리만 나가는 걸 볼 수 있다.

그럼 조건이 안 맞으면? (페이징 0~3, 데이터 4개)

이땐 아까 생략되던 카운트 쿼리가 다시 나오게 된다.

컨트롤러 개발

이제 지금까지 만든 걸 컨트롤러까지 만들어서 이용해 보자.

MemberController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController  
@RequiredArgsConstructor  
public class MemberController { 
 ...
	 private final MemberRepository memberRepository;

 ...
 @GetMapping("/v2/members")  
	public Page<MemberTeamDto> searchMemberV2(MemberSearchCondition condition, Pageable pageable) {  
	    return memberRepository.searchPageSimple(condition, pageable);  
	}  
	  
	@GetMapping("/v3/members")  
	public Page<MemberTeamDto> searchMemberV3(MemberSearchCondition condition, Pageable pageable) {  
	    return memberRepository.searchPageComplex(condition, pageable);  
	}
}

다음과 같이 컨트롤러를 만들어 줬다.

다음과 같이 페이징 정보랑 잘 나온다.

또 v3컨트롤러 같은 경우에는 데이터 보다 페이징 카운트가 크면 카운트 쿼리도 안나간다.

스프링 데이터 정렬(Sort)

스프링 데이터 JPA는 자신의 정렬(Sort)을 Querydsl의 정렬(OrderSpecifier)로 편리하게 변경하는 기능을 제공한다. 이 부분은 뒤에 스프링 데이터 JPA가 제공하는 Querydsl 기능에서 살펴 보자.

스프링 데이터 Sort를 Querydsl의 OrderSpecifier 로 변환

1
2
3
4
5
6
7
8
JPAQuery<Member> query = queryFactory.selectFrom(member);  
  
for (Sort.Order o : pageable.getSort()) {  
    PathBuilder pathBuilder = new PathBuilder(member.getType(), member.getMetadata());  
    query.orderBy(new OrderSpecifier<Comparable>(o.isAscending() ? Order.ASC : Order.DESC,  
            pathBuilder.get(o.getProperty())));  
}  
List<Member> result = query.fetch();

참고 : 정렬(Sort)은 조건이 조금만 복잡해져도 PageableSort기능을 사용하기 어렵다.
루트 엔티티 범위를 넘어가는 동적 정렬 기능이 필요하면 스프링 데이터 페이징이 제공하는
Sort를 사용하기 보다는 파라미터를 받아서 직접 쿼리 처리를 하는 것을 권장한다.

댓글남기기