package study.querydslpractice.repository;
import org.springframework.data.domain.Pageable;
import study.querydslpractice.dto.MemberSearchCondition;
import study.querydslpractice.dto.MemberTeamDto;
import java.util.List;
public interface MemberRepositoryCustom {
List<MemberTeamDto> search(MemberSearchCondition condition);
List<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable);
List<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable);
}
fetchResult를 사용하면 페이징 처리한 데이터와 count를 같이 조회할 수 있다. 하지만 fetchResult를 사용하면 count 쿼리 부분에서 문제가 된다. count에 상관없는 테이블을 조회하는 등의 문제로 성능 이슈가 발생한다. 따라서 현재 Querydsl에서는 fetchResult를 사용하지 않도록 deprecate 된 상태이므로 위에서 작성한 예시 코드처럼 페이징으로 데이터 조회하는 쿼리와 count를 구하는 쿼리를 따로 작성하는 것을 권장한다.
@Override
public Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable) {
List<MemberTeamDto> content = jpaQueryFactory
.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();
Long totalCount = jpaQueryFactory
.select(member.count())
.from(member)
.where(usernameEq(condition.getUsername())
, teamNameEq(condition.getTeamName())
, ageGoe(condition.getAgeGoe())
, ageLoe(condition.getAgeLoe())
)
.fetchOne();
return new PageImpl<>(content, pageable, totalCount);
}
PageImpl은 Spring Data의 Page의 구현제이다.
@DisplayName("searchByWhereTest")
@Test
void searchPageSimpleTest() {
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, 3);
Page<MemberTeamDto> result = memberRepository.searchPageSimple(condition, pageRequest);
assertThat(result.getSize()).isEqualTo(3);
assertThat(result.getContent()).extracting("username").containsExactly("member1", "member2", "member3");
}
fetch()로 content와 totalCount를 분리하는 이유는
조회 쿼리보다 totalCount 쿼리가 단순하게 조회가 가능한 경우(join이 없거나) 쿼리를 분리하여 실행하는 것이 좋다.
만약 fetchResult()로 하나의 쿼리에 content와 totalCount를 가지고 올 수 있는 경우에는
totalCount도 join, where절을 모두 타야하기 때문에 카운트 쿼리를 성능 최적화 할 수 없다
따라서 별로로 쿼리를 작성하는 것이 좋다
전체 카운트를 조회 하는 방법을 최적화 할 수 있으면 이렇게 분리하면 된다. (예를 들어서 전체 카운트를 조회할 때 조인 쿼리를 줄일 수 있다면 상당한 효과가 있다.)
코드를 리펙토링해서 내용 쿼리과 전체 카운트 쿼리를 읽기 좋게 분리하면 좋다.
@Override
public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable) {
List<MemberTeamDto> content = getContent(condition, pageable)
Long totalCount = getTotalCount(condition);
return new PageImpl<>(content, pageable, totalCount);
}
private Long getTotalCount(MemberSearchCondition condition) {
Long totalCount = jpaQueryFactory
.select(member.count())
.from(member)
.where(usernameEq(condition.getUsername())
, teamNameEq(condition.getTeamName())
, ageGoe(condition.getAgeGoe())
, ageLoe(condition.getAgeLoe())
)
.fetchOne();
return totalCount;
}
private List<MemberTeamDto> getContent(MemberSearchCondition condition, Pageable pageable) {
List<MemberTeamDto> content = jpaQueryFactory
.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();
return content;
}
조건에 충족하지 않을 경우 getPage가 카운트 쿼리를 호출하지 않는다
@Override
public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable) {
List<MemberTeamDto> content = getContent(condition, pageable);
JPAQuery<Long> totalCountJPAQuery = getTotalCountJPAQuery(condition);
return PageableExecutionUtils.getPage(content, pageable, () -> totalCountJPAQuery.fetchOne());
}
private JPAQuery<Long> getTotalCountJPAQuery(MemberSearchCondition condition) {
return jpaQueryFactory
.select(member.count())
.from(member)
.where(usernameEq(condition.getUsername())
, teamNameEq(condition.getTeamName())
, ageGoe(condition.getAgeGoe())
, ageLoe(condition.getAgeLoe())
);
}