-
Querydsl (4) - 스프링 데이터 JPA + QuerydslWeb/QueryDSL 2024. 1. 24. 15:35
1. 스프링 데이터 JPA 리포지토리 생성
public interface MemberRepository extends JpaRepository<Member, Long> { List<Member> findByUsername(String username); }
2. 사용자 정의 리포지토리
- 사용자 정의 인터페이스 작성 (이름 맘대로)
- 사용자 정의 인터페이스 구현 (이름 맘대로 -> 원래는 정해져있었음)
- 스프링 데이터 리포지토리에 사용자 정의 인터페이스 상속
public interface MemberRepositoryCustom { List<MemberTeamDto> search(MemberSearchCondition condition); } public class MemberRepositoryImpl implements MemberRepositoryCustom { private final JPAQueryFactory queryFactory; public MemberRepositoryImpl(EntityManager em) { this.queryFactory = new JPAQueryFactory(em); } @Override //회원명, 팀명, 나이(ageGoe, ageLoe) 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가 구현된 인터페이스 찾아가서 구현체 빈으로 등록해준다.
3. 스프링 데이터 JPA와 QueryDsl 페이징 연동
- 스프링 데이터의 Page,Pageable를 활용해보자.
//simple은 모두 한번에 조회, complex는 count를 따로 조회 Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable); Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable);
3.1 simple 조회
@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); }
- fetchResult()를 사용하면된다. fetchResult()는 필요없는 order by는 제거한다.
- PageImpl은 Page의 구현체이다.!
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); }
- 페이징과 총 페이지 수를 따로 분리했다.
- 이렇게 조회하면, count하는 쿼리에 최적화를 좀 넣을 여지가 생긴다. ( 전체 카운트에 조인을 줄일 수 있다면)
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);
- PageableExcutionUtils를 사용하면, count쿼리가 필요없는 경우 count를 하지않는다.
- count가 필요없는 경우는 -> 페이지 시작시, 전체 컨텐츠가, 페이지에서 한번에 보여주는 수 보다 작을 때
-> 마지막 페이지의 경우에도 동일한 상황이면 count를 안한다.
* 스프링부트 3.x부터는 Querydsl 5.0을 사용한다.
-> PageableExecutionUtils는 향후 미지원한다. (패키지만 변경됨 data.support.PageableExecutionUtils)
-> fetchResults(), fetchCount() 향후 미지원
두 메서드는 단순쿼리에서만 잘동작합니다. 따라서 복잡한 쿼리에서 작동하지 않습니다.
count쿼리를 분리하십쇼
Long totalCount = queryFactory //.select(Wildcard.count) //select count(*) .select(member.count()) //select count(member.id) .from(member) .fetchOne();
//.. 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(content, pageable, countQuery::fetchOne);
- 이에 따라 PageableExecutionUtils에 마지막에 fetch불러오는 방식도 달라진다.!
'Web > QueryDSL' 카테고리의 다른 글
Querydsl (3) - 순수 JPA와 Querydsl (0) 2024.01.24 Querydsl (1) - 기본 문법 (0) 2024.01.24