본문 바로가기

강의/실전! Querydsl

Section6. 실무활용 - 스프링 데이터 JPA와 Querydsl

728x90

스프링 데이터 JPA - MemberRepository 생성

  • Interface로 MemberRepository를 만들고 스프링 데이터 JPA를 상속받음
  • findByUsername()을 따로 만들어주면 By~를 스프링 데이터 JPA가 인식해서 다음과 같은 쿼리를 자동으로 실행하게 해줌
    • select * from member where username = ?
public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByUsername(String username);
}

사용자 정의 리포지토리

사용자 정의 리포지토리 사용법

  1. 사용자 정의 인터페이스 작성 (MemberRepositoryCustom.class)
  2. 사용자 정의 인터페이스 구현 (MemberRepositoryImpl.class)
  3. 스프링 데이터 리포지토리에 사용자 정의 인터페이스 상속 (MemberRepsotory extends MemberRepositoryCustom)

스프링 데이터 페이징 활용1 - Querydsl 페이징 연동

스프링 데이터 페이징 활용

  • 스프링 데이터의 Page, Pageable 활용
  • 전체 카운트를 한번에 조회하는 단순한 방법
  • 데이터 내용과 전체 카운트를 별도로 조회하는 방법
  1. 스프링 데이터의 Page, Pageable활용
  • MemberRepositoryImpl.java에 단순 페이징 쿼리 조회하는 로직 추가
  • fetchResults()를 사용하면 count쿼리까지 같이 실행이 된다.
@Override
    public Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable) {
        QueryResults<MemberTeamDto> results = queryFactory
                .select(new QMemberTeamDto(
                        member.id.as("memberId"),
                        member.username,
                        member.age,
                        team.id.as("teamId"),
                        team.name.as("teamName")
                ))
                .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();// count쿼리 함께날림

        List<MemberTeamDto> content = results.getResults();
        long total = results.getTotal();

        return new PageImpl<>(content, pageable, total);

    }
  • TestCode
@Test
    public void searchPageSimpleTest() {
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");

        em.persist(teamA);
        // teamA로 했더니 (Commit시) transaction marked 익셉션이 터졌다.
        // Transaction silently rolled back because it has been marked as rollback-only
        em.persist(teamB);

        Member member1 = new Member("member1", 10, teamA);

        Member member2 = new Member("member2", 20, teamA);
        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);

        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);

        MemberSearchCondition condition = new MemberSearchCondition();
        PageRequest pageRequest = PageRequest.of(0, 3);
//        condition.setAgeGoe(35);
//        condition.setAgeLoe(40);
//        condition.setTeamName("teamB");

        Page<MemberTeamDto> result = memberRepository.searchPageSimple(condition, pageRequest);

        assertThat(result.getSize()).isEqualTo(3);
        assertThat(result.getContent()).extracting("username").containsExactly("member1", "member2", "member3");
    }
  • 결과 (count쿼리가 함께 실행된다.)
/* select
        count(member1) 
    from
        Member member1   
    left join
        member1.team as team */ select
            count(member0_.member_id) as col_0_0_ 
        from
            member member0_ 
        left outer join
            team team1_ 
                on member0_.team_id=team1_.id
2024-02-18 08:05:19.520 TRACE 17128 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([col_0_0_] : [BIGINT]) - [4]
2024-02-18 08:05:19.530 DEBUG 17128 --- [           main] org.hibernate.SQL                        : 
    /* select
        member1.id as memberId,
        member1.username,
        member1.age,
        team.id as teamId,
        team.name as teamName 
    from
        Member member1   
    left join
        member1.team as team */ select
            member0_.member_id as col_0_0_,
            member0_.username as col_1_0_,
            member0_.age as col_2_0_,
            team1_.id as col_3_0_,
            team1_.name as col_4_0_ 
        from
            member member0_ 
        left outer join
            team team1_ 
                on member0_.team_id=team1_.id limit ?
  1. 데이터와 카운트 쿼리를 별도로 조회하는 방법
  • count쿼리를 최적화 하고 싶을 때 분리할 수 있다. (실제 쿼리가 복잡한데 카운트 쿼리는 단순할 때)
  • count쿼리를 분리할 땐 fetch()를 사용해 content를 List로 반환하고 카운트쿼리는 fetchCount()를 사용한다.
@Override
    public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable) {
        List<MemberTeamDto> results = queryFactory
                .select(new QMemberTeamDto(
                        member.id.as("memberId"),
                        member.username,
                        member.age,
                        team.id.as("teamId"),
                        team.name.as("teamName")
                ))
                .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();// count쿼리 함께날림

        long total = queryFactory
                .selectFrom(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                )
                .fetchCount();
        List<MemberTeamDto> content = results;

        return new PageImpl<>(content, pageable, total);
    }

스프링데이터 페이징 활용2 - CountQuery 최적화

  • 때에 따라 카운트 쿼리를 생략
    • 페이지 시작이면서 컨텐츠 사이즈가 페이지 사이즈보다 작을 때
    • 마지막 페이지 일때 (offset + 컨텐츠 사이즈를 더해서 전체 사이즈 구함)
  • PageableExecutionUtils.*getPage*(content, pageable, () -> countQuery.fetchCount()); 을 사용하면 스프링 데이터 JPA가 조건이 만족할 때 사용하는 기능을 제공해준다.
@Override
    public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable) {
        List<MemberTeamDto> results = queryFactory
                .select(new QMemberTeamDto(
                        member.id.as("memberId"),
                        member.username,
                        member.age,
                        team.id.as("teamId"),
                        team.name.as("teamName")
                ))
                .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();// count쿼리 함께날림

        JPAQuery<Member> countQuery = queryFactory
                .selectFrom(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                );
        List<MemberTeamDto> content = results;

        return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchCount);

    }

스프링 데이터 JPA가 제공하는 Querydsl 기능

스프링 데이터 페이징 활용3 - 컨트롤러 개발

  • 페이징 처리하는 로직 확인하기 위한 API 경로를 MemberController에 추가
@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);
    }

Source Path

https://github.com/jang314/querydsl

 

GitHub - jang314/querydsl

Contribute to jang314/querydsl development by creating an account on GitHub.

github.com