ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Querydsl (4) - 스프링 데이터 JPA + Querydsl
    Web/QueryDSL 2024. 1. 24. 15:35

     

    1. 스프링 데이터 JPA 리포지토리 생성

     

    public interface MemberRepository extends JpaRepository<Member, Long> {
     List<Member> findByUsername(String username);
    }

     

    2. 사용자 정의 리포지토리 

     

     - 사용자 정의 인터페이스 작성 (이름 맘대로)

    -  사용자 정의 인터페이스 구현 (이름 맘대로 -> 원래는 정해져있었음)

     - 스프링 데이터 리포지토리에 사용자 정의 인터페이스 상속 

     

    출처: querydsl(김영한) - 인프런

     

    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
Designed by Tistory.