[QueryDSL] 활용 (Spring Data JPA + QueryDSL)
웬만한 기능은 Spring Data JPA가 제공해주고, 동적 쿼리 등 복잡한 구현이 필요할 때 QueryDSL 기술을 사용한다.
따로 Repository를 하나 만들어서 기능을 구현하자.
MemberRepositoryCustom을 인터페이스로 만들고 구현할 기능을 선언한 후 MemerRepositoryImpl에 기능을 구현한다.
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 isEmpty(username) ? null : member.username.eq(username);
}
private BooleanExpression teamNameEq(String teamName) {
return isEmpty(teamName) ? null : team.name.eq(teamName);
}
private BooleanExpression ageGoe(Integer ageGoe) {
return ageGoe == null ? null : member.age.goe(ageGoe);
}
private BooleanExpression ageLoe(Integer ageLoe) {
return ageLoe == null ? null : member.age.loe(ageLoe);
}
}
구현은 JPA를 사용할 때와 동일하게 진행한다. (where 다중 파라미터 사용)
이렇게 Custom Repository를 따로 만드는것도 괜찮지만, 프로젝트 규모가 커지고 너무 복잡해진다 싶으면 조회만을 위한 클래스를 새로 만들어서 분리하는 방식의 설계도 고려해보자.
Spring Data JPA 가 제공하는 페이징 처리도 QueryDSL 과 결합해서 사용할 수 있다.
우선 Custom Repository에 페이징 관련 메서드를 추가해주자.
@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);
}
fetch를 사용하면 데이터를 바로 가져온다. 따라서 fetchResults를 사용해 count 쿼리를 추가로 날리자. (fetch와 fetchCount를 분리해서 사용하는 편이 더 좋은 방법이다)
리스트와 총 개수를 사용해 Page 객체의 구현체를 만들어서 반환한다.
@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)
.leftJoin(member.team, team)
.where(usernameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe()));
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchCount);
}
좀 더 최적화 해 보자.
count 쿼리가 필요하지 않은 경우 굳이 쿼리를 날리지 않아도 되지 않나?
컨텐츠 크기가 페이지 크기보다 작으면? (페이지는 100개인데 컨텐츠가 3개)
굳이 count 쿼리를 날리지 않고 컨텐츠의 크기로 totalCount를 설정해도 상관없다.
또한 마지막 페이지에서도 count 쿼리를 날리지 않아도 괜찮다. (offset + 컨텐츠 크기가 totalCount가 된다)
Spring Data JPA가 제공하는 기능이니 웬만하면 사용해서 성능을 최적화하자.
'Spring > QueryDSL' 카테고리의 다른 글
[QueryDSL] 활용 (JPA + QueryDSL) (0) | 2023.01.11 |
---|---|
[QueryDSL] 중급 문법 (0) | 2023.01.10 |
[QueryDSL] 기본 문법 (0) | 2023.01.09 |
댓글
이 글 공유하기
다른 글
-
[QueryDSL] 활용 (JPA + QueryDSL)
[QueryDSL] 활용 (JPA + QueryDSL)
2023.01.11 -
[QueryDSL] 중급 문법
[QueryDSL] 중급 문법
2023.01.10 -
[QueryDSL] 기본 문법
[QueryDSL] 기본 문법
2023.01.09