Post

Querydsl 학습

Querydsl 학습


스프링부트와 db를 사용한다면 대부분 JPA를 사용할 것이고 인터페이스에 쿼리를 사용했을 것이고, 더 나아가 Querydsl를 쓰는 사람들도 있으며 나 또한 포함되어 있다.
그런데 문득 왜 굳이 Querydsl를 쓰는 것이고 Querydsl를 쓰는 것이 효율적이라고 하는데 ‘왜’라는 것은 전혀 모르고 있었기에 깊이 한번 알아보려고 한다.

QueryDSL?

QueryDSL 은 SQL, JPQL 등 쿼리를 코드로 작성할 수 있게 만드는 프레임워크이다. JPA 뿐만 아니라 SQL, Mongodb, Lucence 등 다양한 언어에 대해서 서비스를 하고 있다.


Why QueryDSL?

보통 쿼리는 DB에서는 SQL방식으로, JAVA에서는 JPQL방식으로 객체(ex-엔티티)를 쿼리를 호출하는 객체지향 쿼리이다.

ex)

1
2
@Query("select m from Member m where m.age > 18")
//'m' 은 별칭이며 'Member'는 테이블명이 아닌 엔티티 객체 이름이기에 조금 신경써야 할 부분

SQL로 변환시 select * from Member(혹은 다른 테이블명) where age > 18;가 되어 나이가 18세를 초과되는 회원 데이터들만 조회가 되는 쿼리가 될 것이다.
그래서 굳이 QueryDSL를 사용해야 하나, JPQL를 그냥 사용하면 되지 않나 라는 의문이 들 수도 있는데 JPQL에는 명확한 단점이 존재한다.

JPQL 단점

  • 쿼리를 문자열로 사용한다. -> 쿼리, 즉 조건이 복잡해져 여러 종류의 속성이 많아져 문자열이 길어진다면 오탈자가 생길 가능성과 관리의 어려움이 크다.

  • 컴파일 단계에서 오류를 확인할 수 없고, 런타임 시 해당 쿼리가 실행되어야지만 오류를 확인할 수 있다.
    반면, QueryDSL 은 코드로서 작성하기 때문에 컴파일 단계에서도 오류를 빠르게 발견할 수 있다.


QueryDSL 설정

내가 사용한 프레임워크/라이브러리의 환경이 이러하다.

  • Spring Boot : 3.1.3
  • JAVA : 17
  • Gradle : 8.2.1
  • QueryDSL : 5.0.0

build.gradle 의존성 및 설정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
buildscript{
   ext{
      queryDslVersion = "5.0.0"
   }
}

dependencies {

   implementation "com.querydsl:querydsl-jpa:5.0.0:jakarta"
   annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
   annotationProcessor "jakarta.annotation:jakarta.annotation-api"
   annotationProcessor "jakarta.persistence:jakarta.persistence-api"

}

def querydslDir = "$buildDir/generated/querydsl"

sourceSets {
   main.java.srcDirs += [ querydslDir ]
}

tasks.withType(JavaCompile) {
   options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir)
}

clean.doLast {
   file(querydslDir).deleteDir()
}

자바 11 버전을 사용하다가 17버전으로 넘어가면서 추가되거나 변경되는 코드들 제법있어서 만약 환경이 바뀔 때마다 체크를 해줘야 할 것 같다.


QueryDSL 어떻게 쓰나?

QClASS 생성

QueryDSL은 엔티티 객체를 기반으로 QCLASS를 생성해, 이 클래스를을 중심으로 쿼리를 작성한다.

이 클래스들을 생성하기 위해서는 Gradle 옵션의 other 탭의 compileJava를 실행하면 QueryDSL@Entity어노테이션이 붙은 엔티티 객체 기반들로 QCLASS가 생성된다.

gradle_java_compile qclass

Repository와 QueryDSL 연동하여 사용

  1. QueryDSL를 사용할 인터페이스 선언

  2. “인터페이스 이름”+Impl 이름으로 클래스를 선언, 또한 QueryRepositorySupport라는 부모 클래스를 상속받도록 한다.

    • 여기서 Impl를 선언할 때 상속을 먼저 받고 인터페이스를 받아야 한다. 그리고 QueryRepositorySupport 를 상속받으면 QueryDSL를 사용할 수 없는데 생성자에 super(엔티티.class)를 호출하면 가능하다.

예시 코드

1
2
3
4
public interface BoardSearch {
    
    Page<Board> searchAll(String[] types, String keyword, Pageable pageable);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class BoardSearchImpl extends QuerydslRepositorySupport implements BoardSearch {

    public BoardSearchImpl(){
        super(Board.class);
    }

    @Override
    public Page<Board> searchAll(String[] types, String keyword, Pageable pageable){

        QBoard board = QBoard.board;
        JPQLQuery query = from(board); // select * from board 

        if((types != null && types.length > 0) && keyword != null){ //where 쿼리 조건 만들어주기

            BooleanBuilder booleanBuilder = new BooleanBuilder();
            for(String type : types){
                switch (type){
                    case "t":
                        booleanBuilder.or(board.title.contains(keyword));
                        break;
                    case "c":
                        booleanBuilder.or(board.content.contains(keyword));
                        break;
                    case "w":
                        booleanBuilder.or(board.user.contains(keyword));
                        break;
                }
            }
            query.where(booleanBuilder); //query에 위에 만든 조건 넣기
        }
        query.where(board.bno.gt(0L)); //id 값 > 0 

        this.getQuerydsl().applyPagination(pageable,query);
        List<Board> list = query.fetch();  // fetch() -> JPQLQuery 실행
        Long count = query.fetchCount();  // fetchCount() -> count 쿼리 실행

        return new PageImpl<>(list, pageable, count);
    }
}

JPQLQuery@Query로 작성했던 JPQL을 코드를 통해서 생성할 수 있게 한다. 이를 통해서 where이나group by 혹은 조인 처리 등이 가능하다.
또한 위의 getQuerydsl().applyPagination() 코드처럼 스프링 데이터가 제공하는 페이징을 Querydsl로 편리하게 변환 해주는 기능을 가지고 있다.
이 코드들을 실행하면 아래와 같은 쿼리들이 출력되면서 실행된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Hibernate: 
    select
        b1_0.bno,
        b1_0.content,
        b1_0.moddate,
        b1_0.regdate,
        b1_0.title,
        b1_0.user 
    from
        board b1_0 
    where
        b1_0.title like ? escape '!' 
        and b1_0.bno>? 
    order by
        b1_0.bno desc limit ?,
        ?
Hibernate: 
    select
        count(b1_0.bno) 
    from
        board b1_0 
    where
        b1_0.title like ? escape '!' 
        and b1_0.bno>?



QueryDSL 그럼 마스터?

여태 QueryDSL를 사용하면서 이 코드들은 왜 입력하며, 어떠한 기능을 가지고 있는지 자세히 모르고 있어 좀 더 이해 할 수 있었지만, 현재 몇가지 쓰고 있는 코드들을 계속 써야 하나 의문이 들기도 하였다.

그 의문 중 하나 뽑자면 위에 쓴 QuerydslRepositorySupport는 페이징을 QueryDSL로 편리하게 사용할 수 있으며 몇 가지 장점들이 있지만, QueryDSL 3.x.버전을 대상으로 만들었기에 현재는 5.x버전을 쓰고 최신 버전으로 나온 기술들을 쓰지 못한다는 점과 많은 부분이 불편하다는 평가가 현재 많다.

또한 BooleanBuilder으로 쿼리 where 조건을 작성하고 있는데, 조건이 더 많아지면 보기 어렵다는 점이 있다고 한다.

이외에도 내가 아직 쓰지도 못한 기술들이 있고, 위에 문제들로 인해 사람들은 개선해서 사용하고 있기에 나 또한 개선하거나 새로운 기술들을 학습하고 구현해 봐야겠다고 생각한다.

This post is licensed under CC BY 4.0 by the author.