[Spring Boot/JPA] QueryDSL : 동적 쿼리 작성하기

2024. 2. 13. 19:33
반응형

 

 

JPA를 사용할 때, QueryDSL을 도입하는 가장 큰 이유는 동적 쿼리 작성에 매우 유연하다는 점이다.

이번 포스팅에서는 동적 쿼리를 작성하는 방법에 대해서 알아보도록 하겠다.

 

 


 

사용할 데이터

 

 

1. Author : Book = 1 : N

Author(저자)는 여러 개의 Book(책)을 가진다.

 

2. Author : Organization = N : 1

Author(저자)는 한곳의 Organization(조직)에 속한다.

 

3. Book : Review = 1 : N

Book(책)은 여러 개의 Review(리뷰)를 가진다.

 

@Entity
@Table(name = "Organization")
@Getter
@Setter
public class Organization {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String orgName;

    @OneToMany(mappedBy = "organization", cascade = CascadeType.ALL)
    private List<Author> authors = new ArrayList<>();

}
@Entity
@Table(name = "Author")
@Getter
@Setter
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
    private List<Book> book = new ArrayList<>();

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "organization_id")
    private Organization organization;

}
@Entity
@Table(name = "Book")
@Getter
@Setter
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private Author author;

    @OneToMany(mappedBy = "book", cascade = CascadeType.ALL)
    private List<Review> reviews = new ArrayList<>();

}
@Entity
@Table(name = "Review")
@Getter
@Setter
public class Review {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String comment;

    @ManyToOne
    @JoinColumn(name = "book_id")
    private Book book;

}

 

 

 


동적 쿼리란

 

동적 쿼리는 실행 시점에 쿼리의 일부가 변경될 수 있는 쿼리를 의미한다.

사용자 입력 또는 프로그램 로직에 따라 쿼리의 조건이나 구조를 동적으로 결정해야 하는 경우에 동적 쿼리의 사용이 필요해진다.

 

예를 들어, 사용자가 필터를 적용하여 검색을 수행하는 경우를 생각해보자. 사용자는 검색 조건을 선택할 수 있고, 이러한 조건에 따라 쿼리의 WHERE 절이 동적으로 변경할 수 있다.

 

Spring에서 메서드 searchAuthor(String name, Integer age)를 호출한다고 생각해보자.

위의 쿼리를 실행했을 때 아래의 세 가지 쿼리로 유동적으로 실행되도록 할 수 있다. 케이스는 아래와 같다.

 

 

 

1. 사용자가 파라미터를 넣지 않은 경우(null, null)

2. 사용자가 이름으로 검색한 경우("AAA", null)

3. 사용자가 이름과 나이로 검색한 경우("AAA, 15)

select * from author;
select * from author
where name = "AAA";
select * from author
where name = "AAA" and age = 15;

 

 

 

동적 쿼리를 작성하는 방법에는 BooleanBuilder와 Where 다중 파라미터를 사용하고 싶다.

 


BooleanBuilder 사용

 

private List<Author> DynamicQueryWithBooleanBuilder(String name, Integer age) {

    BooleanBuilder predicate = new BooleanBuilder();

    if (name != null) {
        predicate.and(author.name.eq(name));
    }

    if (age != null) {
        predicate.and(author.age.eq(age));
    }

    return queryFactory
            .selectFrom(author)
            .where(predicate)
            .fetch();

}

 

BooleanBuilder는 QueryDSL에서 여러 개의 조건을 조합하기 위해 사용되는 빌더 클래스이다.

여러 조건을 동적으로 추가할 수 있어서 name과 age 파라미터가 있는지에 대한 여부에 따라서 조건으로 추가한다.

나중에 이를 where절에 조립한 조건 빌더를 넣어서 판별하게 된다.

  • .and()와 .or() 등으로 논리 연산으로 Where문에 조건을 연결할 수 있다.

 

 

@Test
public void usingDynamicQuery() throws Exception {

    List<Author> authors = DynamicQueryWithBooleanBuilder("John Doe", 25);
    System.out.println(authors);

    authors = DynamicQueryWithBooleanBuilder("Jane Smith", null);
    System.out.println(authors);

    authors = DynamicQueryWithBooleanBuilder(null, null);
    System.out.println(authors);


}

 

세 번의 쿼리를 동적으로 실행해 보았다. 첫번째는 이름과 나이에 대한 조건이 있는 경우, 두번째는 이름에 대한 조건만 있는 경우, 마지막은 조건 없이 전체 선택하는 쿼리를 수행한다.

 

쿼리 결과는 아래에서 차례대로 확인할 수 있다.

 

2024-02-13T19:07:09.061+09:00 DEBUG 37838 --- [    Test worker] org.hibernate.SQL                        : 
    select
        a1_0.id,
        a1_0.age,
        a1_0.gender,
        a1_0.name,
        a1_0.organization_id 
    from
        author a1_0 
    where
        a1_0.name=? 
        and a1_0.age=?
[Author(id=1, name=John Doe, age=25, gender=M)]

2024-02-13T19:07:09.063+09:00 DEBUG 37838 --- [    Test worker] org.hibernate.SQL                        : 
    select
        a1_0.id,
        a1_0.age,
        a1_0.gender,
        a1_0.name,
        a1_0.organization_id 
    from
        author a1_0 
    where
        a1_0.name=?
[Author(id=2, name=Jane Smith, age=40, gender=F)]

2024-02-13T19:07:09.064+09:00 DEBUG 37838 --- [    Test worker] org.hibernate.SQL                        : 
    select
        a1_0.id,
        a1_0.age,
        a1_0.gender,
        a1_0.name,
        a1_0.organization_id 
    from
        author a1_0
[Author(id=1, name=John Doe, age=25, gender=M), Author(id=2, name=Jane Smith, age=40, gender=F), Author(id=3, name=Kim, age=12, gender=M)]

 

 

 

 

 

 

 


Where 다중 파라미터 사용

 

해당 방식은 동적 쿼리를 생성하는 또 다른 방법이다.

이 방법은 조건을 생성하는 데 메소드를 사용하여 더 간결하고 읽기 쉽게 작성된다

 

조건 생성 메소드: nameEq()와 ageEq() 메소드는 각각 주어진 이름과 나이에 대한 조건을 생성한다. 조건이 null이 아닌 경우 해당 조건을 생성하고, 그렇지 않으면 null을 반환한다. Where문에서 해당 메서드들을 조건으로 지정하면, Null일 경우 무시하게 되고, Expression이 있는 경우에만 조건을 판별하게 되므로 선택적인 동적 쿼리를 작성할 수 있다.

 

private List<Author> WhereDynamicParam(String name, Integer age) {
    return queryFactory
            .selectFrom(author)
            .where(nameEq(name), ageEq(age))
            .fetch();
}


private BooleanExpression nameEq(String nameCond) {
    return nameCond != null ? author.name.eq(nameCond) : null;
}

private BooleanExpression ageEq(Integer ageCond) {
    return ageCond != null ? author.age.eq(ageCond) : null;
}

 

 

 

마찬가지로 세 번의 쿼리를 동적으로 수행하면 아래와 같은 결과를 얻어낼 수 있다.

@Test
public void usingWhereDynamicParam() throws Exception {

    List<Author> authors = WhereDynamicParam("John Doe", 25);
    System.out.println(authors);

    authors = WhereDynamicParam("Jane Smith", null);
    System.out.println(authors);

    authors = WhereDynamicParam(null, null);
    System.out.println(authors);

}
2024-02-13T19:07:08.919+09:00 DEBUG 37838 --- [    Test worker] org.hibernate.SQL                        : 
    select
        a1_0.id,
        a1_0.age,
        a1_0.gender,
        a1_0.name,
        a1_0.organization_id 
    from
        author a1_0 
    where
        a1_0.name=? 
        and a1_0.age=?
[Author(id=1, name=John Doe, age=25, gender=M)]

2024-02-13T19:07:08.923+09:00 DEBUG 37838 --- [    Test worker] org.hibernate.SQL                        : 
    select
        a1_0.id,
        a1_0.age,
        a1_0.gender,
        a1_0.name,
        a1_0.organization_id 
    from
        author a1_0 
    where
        a1_0.name=?
[Author(id=2, name=Jane Smith, age=40, gender=F)]

2024-02-13T19:07:08.924+09:00 DEBUG 37838 --- [    Test worker] org.hibernate.SQL                        : 
    select
        a1_0.id,
        a1_0.age,
        a1_0.gender,
        a1_0.name,
        a1_0.organization_id 
    from
        author a1_0
[Author(id=1, name=John Doe, age=25, gender=M), Author(id=2, name=Jane Smith, age=40, gender=F), Author(id=3, name=Kim, age=12, gender=M)]

 

 

 


 

함께 보기

 

1. QueryDSL 설정과 Repository에서의 사용

https://sjh9708.tistory.com/174

 

[Spring Boot/JPA] QueryDSL 설정과 Repository에서의 사용

이번 포스팅에서는 Spring Boot에서 이전에 사용했던 JPQL와 JpaRepository 보다 조금 더 객체지향스럽고 유동적인 동적 쿼리를 작성할 수 있도록 QueryDSL 사용을 위한 설정을 해보도록 하자. QueryDSL에 사

sjh9708.tistory.com

 

2. QueryDSL 기본 문법 

https://sjh9708.tistory.com/175

 

[Spring Boot/JPA] QueryDSL 문법(1) : 기본 검색 (선택, 조건, 정렬, 집계, 그룹화)

이전 포스팅에서 QueryDSL 사용을 Repository에서 할 수 있도록 설정하는 방법에 대해서 다루어 보았었다. 이제 실제로 자주 사용되는 SQL문을 QueryDSL을 통해 작성해보도록 하자. JpaRepository는 인터페이

sjh9708.tistory.com


3. QueryDSL Join 문법

https://sjh9708.tistory.com/178

 

[Spring Boot/JPA] QueryDSL 문법(2) : Join 표현식

이전 포스팅에서 기본적인 QueryDSL의 검색 쿼리 문법에 대해서 살펴보았었다. 이번 포스팅에서는 연관된 다른 릴레이션과의 연산을 수행하는 Join과 Subquery 방법에 대해서 알아보도록 하겠다. 아

sjh9708.tistory.com


4. QueryDSL 서브쿼리와 상수/문자열 조작

https://sjh9708.tistory.com/180

 

[Spring Boot/JPA] QueryDSL 문법(3) : 서브쿼리, 상수/문자열 조작

이전 포스팅에서 기본적인 QueryDSL의 검색 쿼리 문법에 대해서 살펴보았었다. 이번 포스팅에서는 연관된 다른 릴레이션과의 연산을 수행하는 Join과 Subquery 방법에 대해서 알아보도록 하겠다. 아

sjh9708.tistory.com

 

5.  QueryDSL 프로젝션과 Entity > DTO 변환 방법들

https://sjh9708.tistory.com/181

 

[Spring Boot/JPA] Entity > DTO 변환 방법들 및 QueryDSL 프로젝션

이전 포스팅들에서 JPQL, Spring Data JPA Repository, QueryDSL 등을 통해서 데이터를 조회하는 방법들에 대해서 다루어 보았었다. 이번에는 쿼리 결과로 나온 Entity 혹은 Tuple들을 DTO로 매칭하는 방법을 알

sjh9708.tistory.com

 

6.  QueryDSL 동적 쿼리 작성하기

https://sjh9708.tistory.com/182

 

[Spring Boot/JPA] QueryDSL : 동적 쿼리 작성하기

JPA를 사용할 때, QueryDSL을 도입하는 가장 큰 이유는 동적 쿼리 작성에 매우 유연하다는 점이다. 이번 포스팅에서는 동적 쿼리를 작성하는 방법에 대해서 알아보도록 하겠다. 사용할 데이터 1. Auth

sjh9708.tistory.com

 

 

 

 

 

반응형

BELATED ARTICLES

more