[Spring Boot/JPA] QueryDSL : 동적 쿼리 작성하기
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
2. QueryDSL 기본 문법
https://sjh9708.tistory.com/175
3. QueryDSL Join 문법
https://sjh9708.tistory.com/178
4. QueryDSL 서브쿼리와 상수/문자열 조작
https://sjh9708.tistory.com/180
5. QueryDSL 프로젝션과 Entity > DTO 변환 방법들
https://sjh9708.tistory.com/181
6. QueryDSL 동적 쿼리 작성하기
https://sjh9708.tistory.com/182
'Backend > Spring' 카테고리의 다른 글
[Spring Boot] 테스트 : Test Double : 모듈 의존성 격리(Mocking) (0) | 2024.05.16 |
---|---|
[Spring Boot] 테스트 코드 : 시작하기 (JUnit) (0) | 2024.03.05 |
[Spring Boot/JPA] Entity > DTO 변환 방법들 및 QueryDSL 프로젝션 (1) | 2024.02.13 |
[Spring Boot/JPA] QueryDSL 문법(3) : 서브쿼리, 상수/문자열 조작 (1) | 2024.02.01 |
[Spring Boot/JPA] QueryDSL 문법(2) : Join 표현식 (1) | 2024.01.29 |