0. 관련 포스팅
- QueryDSL 설정하기
- QueryDSL DTO 반환 방법
- QueryDSL 동적 쿼리 적용 <현재 포스팅>
- QueryDSL 사용자 정의 레포지토리 적용
- QueryDSL 페이징 적용
1. 개요
QueryDSL 동적 쿼리 생성 시,
BooleanBuilder, Where Parameter 사용 방법에 대해 공부한 내용을 정리한 글이다.
해당 포스팅은 김영한님의 QueryDSL 강의를 보고 작성된 내용이나
추가적인 공부 내용과 견해가 일부 들어가 있으니 틀린 댓글은 언제나 환영!
2. 구현
a. BooleanBuilder
BooleanBuilder의 경우,
우리가 where 문에 적용하고자 하는 조건을 BooleanBuilder 객체에 적용 후 사용하는 방법이다.
@DisplayName("Boolean Builder로 동적 쿼리 만들기")
@Test
void dynamicQuery_booleanBuilder() {
// given
String usernameCond = "Kim";
Integer ageCond = 20;
// when
BooleanBuilder builder = new BooleanBuilder();
// BooleanBuilder 에 조건에 대한 내용을 체이닝하여 연결
if (usernameCond != null) {
builder.and(member.name.eq(usernameCond));
}
if (ageCond != null) {
builder.and(member.age.eq(ageCond));
}
List<Member> fetch = query.select(member)
.from(member)
.where(builder) // 위에서 만든 BooleanBuilder 로 조건문 생성
.fetch();
// then
for (Member member : fetch) {
System.out.println(member.toString());
}
Assertions.assertThat(fetch.size()).isEqualTo(1);
}
위 테스트 코드에서
usernameCond, ageCond 변수가 있을 시
builder.and() 메소드를 통해 조건을 추가해 넣은 코드를 볼수 있다.
(.and(), .or() 등 쿼리 생성에 필요한 메소드들이 구현되어 있다.)
builder 객체에 원하는 조건을 세팅한 후에
query 생성문에서 where() 메소드에 BooleanBuilder 객체를 넣어주면 된다.
해당 테스트 코드를 실행하게 되면 아래처럼 로그를 나타내게 되는데,
우리가 적용하고자 했던 name과 age에 대한 조건이 정상적으로 적용된 모습을 볼수 있다.
b. WhereParameter
이번 방법은 where() 메소드에 미리 생성한 메소드를 파라메터로 적용하는 방법이다.
@DisplayName("Where 파라메터로 동적 쿼리 만들기")
@Test
void dynamicQuery_whereParam() {
// given
String usernameCond = "Kim";
Integer ageCond = 20;
// when
List<Member> results = query.selectFrom(member)
.where(usernameEq(usernameCond), ageEq(ageCond))
.fetch();
// then
for (Member result : results) {
System.out.println(result);
}
Assertions.assertThat(results).hasSizeLessThanOrEqualTo(1);
}
query...where() 부분에 보면
usernameEq(), ageEq() 메소드가 파라메터로 들어가있는 모습을 볼수 있는데
해당 메소드에 대해서 설명하기 전에 실행 결과를 보면
위에서 BooleanBuilder를 사용한 실행 로그와 동일하게 의도한 결과를 나타내는걸 볼수 있다.
'그럼 어떤 메소드를 넣었길래 조건문이 만들어진 것일까?'
아래 해당 메소드에 대한 구현 코드를 보면
BooleanExpression, Predicate 타입으로 반환되는 모습을 볼수 있다.
반환 형태는 일반적인 Q파일에 조건을 적용한 형태인데,
타입만 다르게 적용한 모습을 볼 수 있다.
c. BooleanExpression
BooleanExpression과 Predicate이 둘의 차이는
BooleanExpression 추상 클래스가 Predicate를 Implemente하여 추가적인 메소드를 사용할 수 있는 형태로 구성되어있다.
앞서 QueryDSL을 사용해보았다면 조건을 넣을때 Expression이라는 문구가 익숙할 것이다.
Expression은 Query 인스턴스의 General Type에 입력된 파라메터를 바인딩 하는 형태를 띠고 있다.
앞선 QueryDSL DTO 반환 방법 포스팅에서 사용했던 Projections 또한 FactoryExpression의 Factory Class에 해당한다.
약간 멀리 돌아오긴 했지만,
나는 Predicate 는 조건, Expression은 쿼리 바인딩으로 이해를 했다.
(내가 이해한바가 틀릴수도 있으니 댓글 환영!!)
'그럼 BooleanExpression 은 무슨 역할을 하는 것일까?'
결론적으로 말하면 Predicate가 조건의 역할을 하였다면,
BooleanExpression은 조건을 파라메터로 받고 추가 조건을 체이닝하여 사용할수 있도록 구현되어있는 추상클래스로 보면 될듯하다.
아래 코드를 보면 BooleanExpression 반환 타입인 usernameEq() 메소드에 .or() 메소드를 연결하여 기존에 and 로 적용되었던 부분을 바꾸어 적용하였고 실행 로그를 확인하면 and -> or 로 바뀐 모습을 볼수 있었다.
이렇게 메소드로 관리하였을때,
코드량이 늘어날 수는 있겠지만 기존 조건들을 조합하여 새로운 조건을 만들어내는데 용이하고 또 재활용하여 조건을 넣을 수 있는 장점이 있다.
메소드에 파라메터가 null인 경우에 null로 반환하게끔 하였는데
이때 null이 들어가게 되면 QueryFactory에서 null인 값은 쿼리로 생성하지 않는다.
- Expression 이 반환된 경우 : 조건에 대한 쿼리 생성
- Null 이 반환된 경우 : 쿼리 미 생성
BooleanExpression 에 메소드를 연결하여 할때는 null로 반환해버리면 NullPointerException이 발생하므로 주의!
(null 값이니 당연한 얘기긴하지만...)
이런 경우는 어떻게하지 라는 의문이 생겨서 검색해보았는데,
비슷한 의문을 가지신분이 계서서 포스팅 글 아래 첨부하였다.
https://developer-youn.tistory.com/123
[QueryDSL] BooleanExpression말고 Predicate[]로 조건을 동적으로 처리하기
0. 이 글의 목적 BooleanExpression을 쓰는 게 best practice일 수 있지만 더 심플한 방법을 찾다 보니 가능한 방법을 찾아서 메모 겸 적어두고자 함 1. 상황 querydsl을 이용해 db에서 데이터를 가져오려고 하
developer-youn.tistory.com
3. 마무리
어느 방식을 사용하던 사용자가 편하고 보는사람이 이해하기 편한 형태가 적절한 방법인 것 같다.
개발 스타일이 다 다르다보니 뭐가 맞다 이런건 없지만,
강의에서는 언급해주시긴 했는데 생각해보니 '조건에 대한 메소드를 이름에서 바로 이해하게끔 하고 실제 구현체는 안보는 형태가 좀 더 객체지향에 맞는 형태이지 않나' 싶다.
4. GitHub
GitHub - mk1-p/jpa-sample-code: Test for Spring JPA
Test for Spring JPA. Contribute to mk1-p/jpa-sample-code development by creating an account on GitHub.
github.com
5. 참고 자료
3장. 일반 사용법
Querydsl에서 Query를 생성하려면 표현식 인자를 이용해서 query 메서드를 호출한다. query 메서드는 모듈에 따라 다르고 이미 튜토리얼에서 설명했으므로, 본 절에서는 표현식에 초점을 맞출 것이다.
querydsl.com
'DEV > Spring' 카테고리의 다른 글
[JPA] QueryDSL 페이징 적용 (0) | 2023.11.11 |
---|---|
[JPA] QueryDSL 사용자 정의 레포지토리 적용 (0) | 2023.11.06 |
[JPA] QueryDSL DTO 반환 방법 (0) | 2023.10.23 |
[JPA] QueryDSL 설정하기 (0) | 2023.10.16 |
[JPA] JoinColumns 조회 방식 (0) | 2023.10.12 |