ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Querydsl - where절을 이용한 동적 쿼리와 성능 최적화
    Spring/Querydsl 2022. 3. 26. 18:19
    728x90
    반응형

     

     

    where절을 이용한 동적 쿼리와 성능 최적화

     

     Member와 Team을 동적 쿼리를 이용하여 해당 조건에 맞는 데이터를 가져와 보자

     

    1. 성능 최적화를 위해 DB에서 필요한 데이터만 가져올 수 있는 DTO를 생성한다.

    @Data
    public class MemberTeamDto {
    
        private Long memberId;
        private String username;
        private int age;
        private Long teamId;
        private String teamName;
    
        public MemberTeamDto() {
        }
    
        @QueryProjection	// (1)
        public MemberTeamDto(Long memberId, String username, int age, Long teamId, String teamName) {
            this.memberId = memberId;
            this.username = username;
            this.age = age;
            this.teamId = teamId;
            this.teamName = teamName;
        }
    }
    • (1) : Q-Type을 만들기위한 어노테이션

     

    2. 동적 쿼리에 사용할 검색 조건 클래스 생성

    @Data
    public class MemberSearchCondition {
    
        // 회원명, 팀명, 나이(ageGoe, ageLoe)
    
        private String username;
        private String teamName;
        private Integer ageGoe;
        private Integer ageLoe;
    }

     

    3. Repository에 Where절을 이용한 동적 쿼리 메서드 구현

    public List<MemberTeamDto> searchByWhere(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()),	// (1)
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                )
                .fetch();
    }
    //  BooleanExpression은 and, or 을 조합해서 새로운 BooleanExpression을 만들 수 있다.
    //  또한, 결과 값이 null일 경우 무시하기 때문에 npe를 방지할 수 있다
    private BooleanExpression usernameEq(String username) {	// (2)
        return StringUtils.hasText(username) ? member.username.eq(username) : null;
    }
    
    private BooleanExpression teamNameEq(String teamName) {	// (3)
        return StringUtils.hasText(teamName) ? team.name.eq(teamName) : null;
    }
    
    private BooleanExpression ageGoe(Integer ageGoe) {	// (4)
        return ageGoe != null ? member.age.goe(ageGoe) : null;
    }
    
    private BooleanExpression ageLoe(Integer ageLoe) {	// (5)
        return ageLoe != null ? member.age.loe(ageLoe) : null;
    }
    • (1) : where절의 특징으로 콤마(,)를 사용하면 and 조건으로 처리 된다. 만약 null이면 해당 조건은 제외 된다.
    • (2) : username의 값이 존재하면 조건 추가, null or 빈문자("")일 경우 null 반환
    • (3) : teamName의 값이 존재하면 조건 추가, null or 빈문자("")일 경우 null 반환
    • (4) : ageGoe의 값이 존재하면 조건 추가, null 일 경우 null 반환
    • (5) : ageLoe의 값이 존재하면 조건 추가, null 일 경우 null 반환

     

    (2), (3), (4), (5)을 보면 반환 타입을 BooleanExpression을 사용한 것을 볼 수 있는데 Predicate를 사용하지 않은 이유는 BooleanExpression은 and, or을 조합하여 새로운 BooleanExpression을 만들 때 결과 값이 null일 경우 해당 조건은 제외시켜 NullPointrException을 방지할 수 있기 때문에 BooleanExpression을 사용한다. 


     

    4. 조회 테스트

    @Test
    public void searchTest2() {
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
    
        em.persist(teamA);
        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();
        condition.setAgeGoe(35);
        condition.setAgeLoe(40);
        condition.setTeamName("teamB");
    
        List<MemberTeamDto> results = memberQuerydslRepository.searchByWhere(condition);
    
    
    }

     

    Querydsl 쿼리와 JPQL 쿼리

        /* select
            member1.id,
            member1.username,
            member1.age,
            team.id,
            team.name 
        from
            Member member1   
        left join
            member1.team as team 
        where
            team.name = ?1 
            and member1.age >= ?2 
            and member1.age <= ?3 */ select
                member0_.member_id as col_0_0_,
                member0_.username as col_1_0_,
                member0_.age as col_2_0_,
                team1_.team_id as col_3_0_,
                team1_.name as col_4_0_ 
            from
                member member0_ 
            left outer join
                team team1_ 
                    on member0_.team_id=team1_.team_id 
            where
                team1_.name=? 
                and member0_.age>=? 
                and member0_.age<=?

     

     위 쿼리를 보면 MemberSearchCondition의 username값이 null이므로 검색조건에 제외된 것을 볼 수 있다. 이렇듯 null

    값이면 검색조건에 제외, null이 아닌 데이터 값이 있을 경우 검색 조건에 추가되는 동적 쿼리를 사용할 수 있다.


     

    조회 API 컨트롤러 예시

     

    @RestController
    @RequiredArgsConstructor
    public class MemberController {
    
        private final MemberQuerydslRepository memberQuerydslRepository;
        
        @GetMapping("/v1/members")
        public List<MemberTeamDto> searchMemberV1(MemberSearchCondition condition){
            return memberJpaRepository.search(condition);
        }
    }

     

    요청 예시

    • localhost:8080/v1/members?teamName=team&ageGoe=31&ageLoe=35

     

    파라미터로 검색조건을 넘겨주면 된다.

     

     

     

     


    참고
    BooleanBuilder와 BooleanExpression

    [Querydsl] 다이나믹 쿼리 사용하기 (tistory.com)

    728x90
    반응형

    댓글

Designed by Tistory.