Tools & Libraries/Querydsl

[Querydsl] Querydsl 중급문법 2 (@QueryProjection, 동적쿼리 - BooleanBuilder)

대기업 가고 싶은 공돌이 2024. 9. 11. 18:17

프로젝션 결과 반환 - @QueryProjection

@Data
public class MemberDto {
    private String username;
    private int age;

    public MemberDto(){}

    @QueryProjection
    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

우선 위의 방법을 사용하기 위해선 다음과 같이 생성자에 @QueryProjection 어노테이션을 적어주고

 

Q타입을 생성해줘야 한다.

 

@Generated("com.querydsl.codegen.DefaultProjectionSerializer")
public class QMemberDto extends ConstructorExpression<MemberDto> {

    private static final long serialVersionUID = 1356709634L;

    public QMemberDto(com.querydsl.core.types.Expression<String> username, com.querydsl.core.types.Expression<Integer> age) {
        super(MemberDto.class, new Class<?>[]{String.class, int.class}, username, age);
    }

}

 

이렇게 Q타입이 생성된 것을 확인할 수 있다.

 

@Test
public void findDtoByProjection(){
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);

    List<MemberDto> result = queryFactory
            .select(new QMemberDto(member.username, member.age))
            .from(member)
            .fetch();

    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}

 

이후 다음과 같이 select 절에 q타입을 사용해주면 된다.

new QMemberDto로 생성자를 이용해 member의 인자를 넘겨주면 된다.

 

생성자와 거의 동일한 방식이지만 프로젝션을 통해 dto로 반환하면,

컴파일 시점에 오류를 잡아낼 수 있다.

 

프로젝션 단점

  1. Q타입을 생성해줘야 함
  2. Q타입을 생성하기 위해 dto 클래스에 @QueryProjection을 입력해야 했다
    이는 dto 클래스들이 querydsl 라이브러리에 의존성을 갖게 된다는 것이다.
    만약 querydsl을 중간에 빼버린다면 dto도 전부 수정해야할 것이다.

동적 쿼리

동적 쿼리를 해결할 수 있는 방법은 두 가지가 있다.

  1. BooleanBuilder
  2. Where절에 다중 파라미터 사용

BooleanBuilder 사용

@Test
public void dynamicQuery_BooleanBuilder(){
    String usernameParam = "member1";
    Integer ageParam = 10;

    // 동적 쿼리로 이름이 member1이고 나이가 10살인 사람을 찾고 싶은 상황

    List<Member> result = searchMember1(usernameParam,ageParam);

    Assertions.assertThat(result.size()).isEqualTo(1);
}

 

우선 멤버 이름과 나이를 파라미터로 넘겨주어 멤버 리스트를 받아야 하는 상황이다.

private List<Member> searchMember1(String usernameCond, Integer ageCond){
    //파라미터 값이 널이냐 아니냐에 따라서 동적으로 쿼리가 바뀌어야 함
    //나이만 널이면 이름만 일치하는 사람을 찾아서 반환
    //이름이 널이면 나이가 일치하는 사람을 찾아서 반환
    // 둘 다 널이면 where 문에 조건이 없어야함

    JPAQueryFactory queryFactory = new JPAQueryFactory(em);

    BooleanBuilder builder = new BooleanBuilder();

    if(usernameCond != null){
        builder.and(member.username.eq(usernameCond));
    }
    if(ageCond != null){
        builder.and(member.age.eq(ageCond));
    }

    return queryFactory
            .selectFrom(member)
            .where(builder)
            .fetch();
}

 

//파라미터 값이 널이냐 아니냐에 따라서 동적으로 쿼리가 바뀌어야 함
//나이만 널이면 이름만 일치하는 사람을 찾아서 반환
//이름이 널이면 나이가 일치하는 사람을 찾아서 반환
// 둘 다 널이면 where 문에 조건이 없어야함

위의 조건 처럼 동적 쿼리를 작성해야 하는데 BooleanBuilder를 활용해서 간단하게 해결할 수 있다.

 

builder에는 and와 or 조건을 설정할 수 있는데 이를 이용해 조건을 걸 것이다.

if(usernameCond != null){
    builder.and(member.username.eq(usernameCond));
}
if(ageCond != null){
    builder.and(member.age.eq(ageCond));
}

 

이렇게 이름이 null이 아니면 and 조건을 추가해주고

 

나이도 null이 아니면 and 조건을 추가해줬다.

 

만약 null인 인자가 들어왔다면 조건이 추가되지 않으므로 조건이 추가되지 않을 것이다.

 

이제 해당 builder를 where 절에 넣어주기만 하면 된다.

 

return queryFactory
        .selectFrom(member)
        .where(builder)
        .fetch();

 

조건이 두 개 다 잘 들어간 것을 확인할 수 있다.

 

그럼 age 값에 null을 넣어 보겠다.

 

조건이 추가되지 않았다.

 

동적 쿼리가 무사히 만들어졌다.

 

BooleanBuilder에 인자 넣기

BooleanBuilder builder = new BooleanBuilder(member.username.eq(usernameCond));

 

인자에 값을 넣어 해당 조건을 필수로 만들 수도 있다.

 

만약 유저 이름은 무조건 필수로 들어가야 한다면

 

위와 같이 BooleanBuilder에 조건을 넣어서 해당 조건을 필수로 설정할 수도 있다.

 

null이 허용인지 아닌지는 앞에서 따로 코드를 통해 방어해줘야 한다.