이전 포스팅에서 작성했던 순수 JPA를 아래와 같이 Spring Data JPA로 변경해 보자.
public interface MemberRepository extends JpaRepository<Member, Long>, MemberCustomRepository {
List<Member> findByUsername(String username);
}
MemberRepositoryCustom
👉 MemberCustomRepository
변경MemerRepositoryImpl
👉 MemberCustomRepositoryImpl
변경public interface MemberCustomRepository {
List<MemberTeamDto> search(MemberSearchCondition condition);
}
public class MemberCustomRepositoryImpl implements MemberCustomRepository{
private final JPAQueryFactory queryFactory;
public MemberRepositoryImpl(JPAQueryFactory queryFactory) {
this.queryFactory = queryFactory;
}
@Override
public List<MemberTeamDto> search(MemberSearchCondition condition) {
return 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())
)
.fetch();
}
private BooleanExpression usernameEq(String username) {
return isEmpty(username)?null:member.username.eq(username);
}
private BooleanExpression teamNameEq(String teamName) {
return hasText(teamName) ? team.name.eq(teamName):null;
}
private BooleanExpression ageGoe(Integer ageGoe) {
return ageGoe!= null ? member.age.goe(ageGoe):null;
}
private BooleanExpression ageLoe(Integer ageLoe) {
return ageLoe!= null ? member.age.loe(ageLoe):null;
}
}
public interface MemberRepository extends JpaRepository<Member, Long>, MemberCustomRepository {
List<Member> findByUsername(String username);
}
Spring Data의 Page
, Pageable
을 활용해 보자.
✅ 전체 카운트를 한 번에 조회하는 단순한 방법
✅ 데이터 내용과 전체 카운트를 별도로 조회하는 방법
public interface MemberRepositoryCustom {
List<MemberTeamDto> search(MemberSearchCondition condition);
Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable);
Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable);
}
전체 카운트를 한 번에 조회하는 단순한 방법 - searchPageSimple(), fetchResults() 사용
// 단순한 페이징, fetchResults() 사용
@Override
public Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable) {
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);
}
Querydsl이 제공하는 fetchResults()
를 사용하면 내용과 전체 카운트를 한 번에 조회할 수 있다.
(실제 쿼리는 2번 호출)
fetchResult()
는 카운트 쿼리 실행 시 필요 없는 order by
는 제거한다.
데이터 내용과 전체 카운트를 별도로 조회하는 방법 - searchPageComplex()
/**
* 복잡한 페이징
* 데이터 조회 쿼리와, 전체 카운트 쿼리를 분리
*/
@Override
public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable) {
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();
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);
}
전체 카운트를 조회하는 방법을 최적화할 수 있으면 이렇게 분리하면 된다.
조인 쿼리를 줄일 수 있다면
상당한 효과가 있다. JPAQuery<Member> countQuery = queryFactory
.select(member)
.from(member)
.leftJoin(member.team, team)
.where(usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe()));
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchCount);
페이지 시작이면서 컨텐츠 사이즈가 페이지 사이즈보다 작을 때
마지막 페이지 일 때 (offset + 컨텐츠 사이즈를 더해서 전체 사이즈 구함)
Spring Data JPA는 정렬(Sort)을 Querydsl의 정렬(OrderSpecifier)로 편리하게 변경하는
기능을 제공한다.
Spring Data의 정렬을 Querydsl의 정렬로 직접 전환하는 방법은 아래의 코드를 참고하자
JPAQuery<Member> query = queryFactory
.selectFrom(member);
for (Sort.Order o : pageable.getSort()) {
PathBuilder pathBuilder = new PathBuilder(member.getType(), member.getMetadata());
query.orderBy(new OrderSpecifier(o.isAscending() ? Order.ASC : Order.DESC, pathBuilder.get(o.getProperty())));
}
List<Member> result = query.fetch();
정렬(Sort
)은 조건이 조금만 복잡해져도 Pageable
의 Sort
기능을 사용하기 어렵다.
루트 엔티티 범위를 넘어가는 동적 정렬 기능이 필요하면 스프링 데이터 페이징이 제공하는 Sort
를 사용하기보다는 파라미터를 받아서 직접 처리하는 것을 권장한다.