https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84/
강의를 들으며 생각 정리
이전 포스팅에서 이어진다.
2021.06.04 - [querydsl] - [Querydsl] 실무 활용 - 순수 JPA와 Querydsl
스프링 데이터 JPA 리포지토리로 변경
</repository/MemberRepository>
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsername(String username);
}
-> 스프링 데이터 JPA를 사용하면 순수 JPA로 만든 리포지토리에 비해 코드 길이가 훨씬 줄었고, 쿼리 메서드 이름으로 생성 등 다양한 기능을 사용할 수 있다.
사용자 정의 리포지토리
스프링 데이터 JPA 리포지토리는 인터페이스이기 때문에 Querydsl 기능으로 메서드를 정의할 수 없다.
-> 따로 사용자 정의 리포지토리를 상속해야 한다.
1. 사용자 정의 인터페이스 작성
public interface MemberRepositoryCustom {
List<MemberTeamDto> search(MemberSearchCondition condition);
}
2. 사용자 정의 인터페이스 구현
public class MemberRepositoryImpl implements MemberRepositoryCustom {
private final JPAQueryFactory queryFactory;
public MemberRepositoryImpl(EntityManager em) {
this.queryFactory = new JPAQueryFactory(em);
}
@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 hasText(username) ? member.username.eq(username) : null;
}
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;
}
}
사용자 정의 인터페이스 구현체 클래스명은 다음 중 하나로 정해야 한다.
- 스프링 데이터 JPA 인터페이스명 + "Impl" : MemberRepositoryImpl
- 사용자 정의 인터페이스명 + "Impl" : MemberRepositoryCustomImpl
3. 스프링 데이터 리포지토리에 사용자 정의 인터페이스 상속
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
List<Member> findByUsername(String username);
}
이제 MemberRepository로 스프링 데이터 JPA와 Querydsl 기능을 같이 사용할 수 있다.
+) 참고
스프링 데이터 JPA와 Querydsl을 함께 사용하는 방법은 기본적으로 사용자 정의 리포지토리를 상속하는 것이다.
그러나 화면용 쿼리처럼 쿼리가 엄청 복잡해지면 복잡한 쿼리 전용 리포지토리를 따로 만들어도 된다.
ex) MemberQueryRepository
공용성이 있는 핵심 비즈니스 쿼리는 MemberRepository를 사용하고
공용성이 없고 화면을 위한 복잡한 쿼리는 MemberQueryRepository를 사용하는 것이다.
스프링 데이터 페이징 활용1 - Querydsl 페이징 연동
페이징을 위해 스프링 데이터의 Page, Pageable을 Querydsl에서 활용하는 방법을 알아보자.
<사용자 정의 인터페이스에 페이징 메서드 2가지 추가>
public interface MemberRepositoryCustom {
List<MemberTeamDto> search(MemberSearchCondition condition);
Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable);
Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable);
}
1. 전체 카운트를 한 번에 조회하는 단순한 방법
@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);
}
pageable에서 offset, limit 정보를 추출해서 쿼리 조건에 추가한다.
fetchResults()를 사용하면 내용과 전체 카운트를 한 번에 조회할 수 있다. (실제 쿼리가 2번 호출 된다)
2. 데이터 내용과 전체 카운트를 별도로 조회하는 방법
@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)
.where(
usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
)
.fetchCount();
return new PageImpl<>(content, pageable, total);
}
전체 카운트를 구할 때는 조인과 같은 성능에 영향을 주는 쿼리가 필요 없을 때가 있다.
그럴 때는 이렇게 카운트 쿼리를 분리하면 상당한 효과가 있다.
+) 코드를 리팩토링해서 내용 쿼리와 전체 카운트 쿼리를 읽기 좋게 분리하면 좋다.
스프링 데이터 페이징 활용2 - CountQuery 최적화
스프링 데이터 라이브러리는 count 쿼리가 생략 가능한 경우 생략해서 처리
1. 페이지 시작이면서 컨텐츠 사이즈가 페이지 사이즈보다 작을 때
2. 마지막 페이지일 때(offset + 컨텐츠 사이즈를 더해서 전체 사이즈 구함)
@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();
JPAQuery<Member> countQuery = queryFactory
.select(member)
.from(member)
.where(
usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
);
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchCount);
// return new PageImpl<>(content, pageable, total);
}
PageableExecutionUtils을 사용하면 위에서 언급한 두 조건을 고려해서 카운트 쿼리를 fetchCount한다.
스프링 데이터 페이징 활용3 - 컨트롤러 개발
@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);
}
+) 참고: 스프링 데이터 정렬(Sort)
스프링 데이터 JPA는 자신의 정렬(Sort)을 Querydsl의 정렬(OrderSpecifier)로 편리하게 변경하는 기능을 제공한다. 이 부분은 뒤에 스프링 데이터 JPA가 제공하는 Querydsl 기능에서 살펴보겠다.
스프링 데이터의 정렬을 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를 사용하기 보다는 파라미터를 받아서 직접 처리하는 것을 권장한다.
'java > querydsl' 카테고리의 다른 글
[Querydsl] 스프링 데이터 JPA가 제공하는 Querydsl 기능 (2) | 2021.06.07 |
---|---|
[Querydsl] 실무 활용 - 순수 JPA와 Querydsl (0) | 2021.06.04 |
[Querydsl] 중급 문법 (1) | 2021.06.03 |
[Querydsl] 기본 문법 (0) | 2021.06.02 |
[Querydsl] 예제 도메인 모델 (1) | 2021.06.01 |