본문 바로가기

개인적으로 공부한 것을 정리해 놓은 블로그입니다 틀린 것이 있으면 댓글 부탁 드립니다!


JPA/QueryDSL

QueryDSL 공부 9 - 동적쿼리 성능 최적화 (Builder 와 where 절 동적쿼리)

반응형

이전에 BooleanBuiler와 where 절 을 통해 동적쿼리를 만드는 방법을 알아 봤었는데 

 

실제 repository에서  동적 쿼리와 Dto 조회를 함께 사용하는 방법을 알아보자 

 

일단 두가지 방법에 공통적으로 사용될 검색 키워드를 담을 SearchCond 라는 Dto와 

 

반환되는 데이터를 담을 MemberTeamDto를 만들었다 

 

 

MemberTeamDto

 

@Data
public class MemberTeamDto{

    private Long memberId;
    private int age;
    private String username;
    private String teamName;

    @QueryProjection
    public MemberTeamDto(Long memberId, int age, String username, String teamName) {
        this.memberId = memberId;
        this.age = age;
        this.username = username;
        this.teamName = teamName;
    }
}

 

- @QueryProjection을 통해서 Dto 조회를 하려한다 . Dto 생성후 gradle에서 compileQueryDsl로 Q타입 객체가 생성되 도록 해야한다 .

 

 

SearchCond

 

package com.querydsl.study.dto;


import lombok.Data;

@Data
public class SearchCond {

    private String username;
    private String teamName;
    private Integer ageLoe;
    private Integer ageGoe;
}

 

 -검색 조건을 담는 Dto이다 각 필드가 조건의 파라미터가 된다 . 만약 필드에 값이 null 이라면 해당 조건은 무시된다. 

 

 

 

1. BooleanBuilder 를 통한 동적 쿼리 

 

 

MemberJpaRepository

    public List<MemberTeamDto> findByCondition(SearchCond cond){

        BooleanBuilder builder = new BooleanBuilder();
        if(cond.getUsername() != null){
            builder.and(member.username.eq(cond.getUsername()));
        }
        if(cond.getTeamName() != null){
            builder.and(team.name.eq(cond.getTeamName()));
        }
        if (cond.getAgeGoe() != null){
            builder.and(member.age.goe(cond.getAgeGoe()));
        }
        if (cond.getAgeLoe() != null){
            builder.and(member.age.loe(cond.getAgeLoe()));
        }


        return queryFactory.select(
                new QMemberTeamDto(
                        member.id.as("memberId"),
                        member.age,
                        member.username,
                        team.name.as("teamName")
                        ))
                .from(member)
                .leftJoin(member.team , team)
                .where(builder)
                .fetch();
    }

 

BooleanBuilder 생성 후에  조건이 존재하는지 체크해주고 있다면 해당 조건을 builder에 쌓는다 

where 절에 만들어진 builder를  넣어주면 된다. 

 

테스트 해보자

 

 


    @BeforeEach
    public void initDB(){
        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);
    }


    @Test
    void searchByBuilder(){
        SearchCond searchCond = new SearchCond();
        searchCond.setAgeGoe(40);
        searchCond.setAgeLoe(40);
        searchCond.setUsername("member4");
        searchCond.setTeamName("teamB");

        List<MemberTeamDto> byCondition = memberJpaRepository.findByCondition(searchCond);

        for (MemberTeamDto memberTeamDto : byCondition) {
            System.out.println("memberTeamDto = " + memberTeamDto);
        }

        assertThat(byCondition.size()).isEqualTo(1);
        assertThat(byCondition).extracting("username").containsExactly("member4");
    }

 

먼저  @BeforeEach로 test데이터가 채워 지도록했다.

 

SearchCond를 생성하여 검색조건을 채우고   findByCondition의 인자로 넘기겼다.  

 

검색조건은 40살 이상 40살이하 이름이 member4이고 소속팀의 이름이 teamB인 멤버를 찾는다.

 

반환되는 것을 출력하고 

 

검색되는 리스트의 크기와 , 유저이름을 추출해 예상하는 멤버(member4)를 포함하는지 테스트 해봤다.

 

 테스트는 통과 되었고 

 

출력되는 쿼리와 출력된 데이터를 살펴보자

 

//JPQL

/*  select
        member1.id as memberId,
        member1.age,
        member1.username,
        team.name as teamName 
    from
        Member member1   
    left join
        member1.team as team 
    where
        member1.username = ?1 
        and team.name = ?2 
        and member1.age >= ?3 
        and member1.age <= ?3 */
        
//SQL        
        select
            member0_.member_id as col_0_0_,
            member0_.age as col_1_0_,
            member0_.username as col_2_0_,
            team1_.name as col_3_0_ 
        from
            member member0_ 
        left outer join
            team team1_ 
                on member0_.team_id=team1_.team_id 
        where
            member0_.username=? 
            and team1_.name=? 
            and member0_.age>=? 
            and member0_.age<=?
            
//검색 데이터           
memberTeamDto = MemberTeamDto(memberId=6, age=40, username=member4, teamName=teamB)

 

검색 조건이 잘들어가 JPQL과 SQL 이 나갔다 . 검색데이터도 검색조건에 맞는 데이터가 출력됬다 . 

 

 

 

2. where 절 파라미터를 통한 동적쿼리

 

 

MemberJpaRepository

 

 public List<MemberTeamDto> searchByWhere(SearchCond cond){

        return queryFactory.select(
                new QMemberTeamDto(
                        member.id.as("memberId"),
                        member.age,
                        member.username,
                        team.name.as("teamName")
                ))
                .from(member)
                .leftJoin(member.team , team)
                .where(
                        usernameEq(cond.getUsername()),
                        teamNameEq(cond.getTeamName()),
                        ageGoe(cond.getAgeGoe()),
                        ageLoe(cond.getAgeLoe())
                )
                .fetch();
    }

    private BooleanExpression ageLoe(Integer ageLoe) {
        return ageLoe != null ? member.age.loe(ageLoe) : null;
    }

    private BooleanExpression ageGoe(Integer ageGoe) {
        return ageGoe != null ? member.age.goe(ageGoe) : null;
    }

    private BooleanExpression teamNameEq(String teamName) {
        return hasText(teamName) ? team.name.eq(teamName) : null;
    }

    private BooleanExpression usernameEq(String username) {
        return hasText(username) ? member.username.eq(username) : null;
    }
    
    private BooleanExpression ageBetween(Integer ageGoe, Integer ageLoe){
        if (ageGoe == null || ageLoe == null){
            return null;
        }
        return member.age.goe(ageGoe).and(member.age.loe(ageLoe));
    }

 

Builder로 만든 방식과 다르게 where 절에 직접 만든 메서드를 통해 컨디션을 추가한다 .

 

메서드에서는 컨디션의 null 여부를 따져 검색조건을 반환한다. 

 

BooleanExpression 으로 리턴타입을 하면 ageBetween 처럼  여러개의 메서드를 조합하여 사용할 수도 있다 .

 

이떄 해당 파라미터에 대한 null 체크가 필요하다 . 

 

반응형