BooleanBuilder
BooleanBuilder는 Predicate를 구현하는 구현체이고 Predicate는 where절의 파라미터 타입입니다.
따라서 BooleanBuilder를 이용해 조건절을 추가한 뒤 where절에 전달하면되고, 이 부분을 동적으로 구현할 수 있습니다.
현재 Entity는 필드 수가 워낙 적어 경우의 수가 몇 가지 나오지 않으니 한 번 모두 테스트해보도록 하겠습니다.
@SpringBootTest
@Transactional
class PlayerTest {
@Autowired
EntityManager entityManager;
private JPAQueryFactory queryFactory;
@BeforeEach
void setup() {
Team tottenhamHotspur = new Team("Tottenham Hotspur F.C.");
Team manchesterCity = new Team("Manchester City F.C.");
entityManager.persist(tottenhamHotspur);
entityManager.persist(manchesterCity);
Player harryKane = new Player("Harry Kane", 27, tottenhamHotspur);
Player heungminSon = new Player("Heungmin Son", 29, tottenhamHotspur);
Player kevinDeBruyne = new Player("Kevin De Bruyne", 30, manchesterCity);
Player raheemSterling = new Player("Raheem Shaquille Sterling", 26, manchesterCity);
entityManager.persist(harryKane);
entityManager.persist(heungminSon);
entityManager.persist(kevinDeBruyne);
entityManager.persist(raheemSterling);
queryFactory = new JPAQueryFactory(entityManager);
}
@Test
void simpleQuerydslWithBooleanBuilder() {
// given
List<QueryParam> queryParams = new ArrayList<>(); // (1)
queryParams.add(QueryParam.of("Heungmin Son", 29));
queryParams.add(QueryParam.of("Heungmin Son", null));
queryParams.add(QueryParam.of(null, 29));
queryParams.add(QueryParam.of(null, null));
// when
for (QueryParam queryParam : queryParams) { // (2)
List<Player> players = queryFactory
.selectFrom(player)
.where(whereClause(queryParam))
.fetch();
players.forEach(System.out::println);
}
}
private Predicate whereClause(QueryParam queryParam) { // (3)
BooleanBuilder booleanBuilder = new BooleanBuilder();
Optional.ofNullable(queryParam.getName())
.ifPresent(name -> booleanBuilder.and(player.name.eq(name)));
Optional.ofNullable(queryParam.getAge())
.ifPresent(age -> booleanBuilder.and(player.age.lt(age)));
return booleanBuilder;
}
}
(1) 쿼리에 사용할 파라미터를 이름과 나이 둘 다 가지는 경우, 이름만 가지는 경우, 나이만 가지는 경우, 둘 다 가지지 않는 경우, 네 가지 케이스로 생성합니다.
(2) 네 가지 경우 모두 테스트하기 위해 반복문 안에서 처리하였습니다.
(3) where 절을 BooleanBuilder를 이용해 구성합니다. 파라미터의 존재 여부에 따라 BooleanBuilder에 and 조건으로 추가하였습니다.
2021-07-23 09:14:06.813 DEBUG 8035 --- [ main] org.hibernate.SQL :
/* select
player
from
Player player
where
player.name = ?1
and player.age < ?2 */ select
player0_.player_id as player_i1_1_,
player0_.age as age2_1_,
player0_.name as name3_1_,
player0_.team_id as team_id4_1_
from
player player0_
where // (1)
player0_.name=?
and player0_.age<?
2021-07-23 09:14:06.821 DEBUG 8035 --- [ main] org.hibernate.SQL :
/* select
player
from
Player player
where
player.name = ?1 */ select
player0_.player_id as player_i1_1_,
player0_.age as age2_1_,
player0_.name as name3_1_,
player0_.team_id as team_id4_1_
from
player player0_
where // (2)
player0_.name=?
Player(id=4, name=Heungmin Son, age=29)
2021-07-23 09:14:06.846 DEBUG 8035 --- [ main] org.hibernate.SQL :
/* select
player
from
Player player
where
player.age < ?1 */ select
player0_.player_id as player_i1_1_,
player0_.age as age2_1_,
player0_.name as name3_1_,
player0_.team_id as team_id4_1_
from
player player0_
where // (3)
player0_.age<?
Player(id=3, name=Harry Kane, age=27)
Player(id=6, name=Raheem Shaquille Sterling, age=26)
2021-07-23 09:14:06.852 DEBUG 8035 --- [ main] org.hibernate.SQL :
/* select
player
from
Player player */ select
player0_.player_id as player_i1_1_,
player0_.age as age2_1_,
player0_.name as name3_1_,
player0_.team_id as team_id4_1_
from
player player0_
// (4)
Player(id=3, name=Harry Kane, age=27)
Player(id=4, name=Heungmin Son, age=29)
Player(id=5, name=Kevin De Bruyne, age=30)
Player(id=6, name=Raheem Shaquille Sterling, age=26)
- 1. name, age 조건이 모두 생성되었습니다.
- 2. name 조건만 생성되었습니다.
- 3. age 조건만 생성되었습니다.
- 4. 두 조건이 모두 없으므로 where 절이 생성되지 않았습니다.
where 메서드 파라미터 사용
바로 위에 언급했듯이 where 절에는 Predicate를 0개에서 N개까지 전달 가능합니다.
BooleanBuilder를 이용했을 경우 N개가 하나로 합쳐진 Predicate 한 개를 전달하는 방식인데요, 여러 개를 전달할 경우 아래처럼 구현할 수 있습니다.
@Test
void simpleQuerydslWithDynamicQueryUsingWhereClause() {
// given
List<QueryParam> queryParams = new ArrayList<>();
queryParams.add(QueryParam.of("Heungmin Son", 29));
queryParams.add(QueryParam.of("Heungmin Son", null));
queryParams.add(QueryParam.of(null, 29));
queryParams.add(QueryParam.of(null, null));
// when
for (QueryParam queryParam : queryParams) {
List<Player> players = queryFactory
.selectFrom(player)
.where(Optional.ofNullable(queryParam.getName()) // (1)
.map(player.name::eq)
.orElse(null),
Optional.ofNullable(queryParam.getAge()) // (2)
.map(player.age::lt)
.orElse(null))
.fetch();
players.forEach(System.out::println);
}
}
(1) 쿼리 파라미터중 name이 있을 경우 player.name과 비교하고 없으면 null을 전달합니다.
(2) 쿼리 파라미터중 age이 있을 경우 player.age과 비교하고 없으면 null을 전달합니다.
BooleanBuilder를 사용했을 때와 동일한 결과를 확인할 수 있습니다.
2021-07-23 09:28:45.716 DEBUG 8086 --- [ main] org.hibernate.SQL :
/* select
player
from
Player player
where
player.name = ?1
and player.age < ?2 */ select
player0_.player_id as player_i1_1_,
player0_.age as age2_1_,
player0_.name as name3_1_,
player0_.team_id as team_id4_1_
from
player player0_
where
player0_.name=?
and player0_.age<?
2021-07-23 09:28:45.728 DEBUG 8086 --- [ main] org.hibernate.SQL :
/* select
player
from
Player player
where
player.name = ?1 */ select
player0_.player_id as player_i1_1_,
player0_.age as age2_1_,
player0_.name as name3_1_,
player0_.team_id as team_id4_1_
from
player player0_
where
player0_.name=?
Player(id=4, name=Heungmin Son, age=29)
2021-07-23 09:28:45.758 DEBUG 8086 --- [ main] org.hibernate.SQL :
/* select
player
from
Player player
where
player.age < ?1 */ select
player0_.player_id as player_i1_1_,
player0_.age as age2_1_,
player0_.name as name3_1_,
player0_.team_id as team_id4_1_
from
player player0_
where
player0_.age<?
Player(id=3, name=Harry Kane, age=27)
Player(id=6, name=Raheem Shaquille Sterling, age=26)
2021-07-23 09:28:45.764 DEBUG 8086 --- [ main] org.hibernate.SQL :
/* select
player
from
Player player */ select
player0_.player_id as player_i1_1_,
player0_.age as age2_1_,
player0_.name as name3_1_,
player0_.team_id as team_id4_1_
from
player player0_
Player(id=3, name=Harry Kane, age=27)
Player(id=4, name=Heungmin Son, age=29)
Player(id=5, name=Kevin De Bruyne, age=30)
Player(id=6, name=Raheem Shaquille Sterling, age=26)
Optional이 너무 지저분하다고 느껴지시면 메서드로 추출하여 더 간단하게 표현할 수 있습니다.
@Test
void simpleQuerydslWithDynamicQueryUsingWhereClause() {
// given
List<QueryParam> queryParams = new ArrayList<>();
queryParams.add(QueryParam.of("Heungmin Son", 29));
queryParams.add(QueryParam.of("Heungmin Son", null));
queryParams.add(QueryParam.of(null, 29));
queryParams.add(QueryParam.of(null, null));
// when
for (QueryParam queryParam : queryParams) {
List<Player> players = queryFactory
.selectFrom(player)
.where(condition(queryParam.getName(), player.name::eq),
condition(queryParam.getAge(), player.age::lt))
.fetch();
players.forEach(System.out::println);
}
}
private <T> Predicate condition(T value, Function<T, Predicate> function) {
return Optional.ofNullable(value)
.map(function)
.orElse(null);
}
단, 이렇게 전달하게되면 모든 조건이 and로 묶이게 됩니다.
where절에 Predicate를 여러 개 넘길 경우 기본 동작이 and이기 때문인데요, where 절 구현 내용을 쭉 따라서 올라가보면,
마지막에 and를 호출하는 것을 확인할 수 있습니다.
따라서 or를 사용하려면 어쩔 수 없이 BooleanBuilder를 사용해야 합니다.
보통 동적 쿼리를 사용할 때는 and 조건을 사용하는 경우가 많기 때문에 or를 기본 조건으로 사용하는 등의 예외상황만 따로 처리한다면 위 방식으로 사용하는데 큰 문제는 없을 거 같습니다.
OR를 사용하려면 BooleanBuilder !!!
'개-발 > Database' 카테고리의 다른 글
[PostgreSQL] 테이블 조회 (시스템 테이블 제외) (0) | 2023.03.26 |
---|---|
[QueryDSL] 동적쿼리 BooleanBuilder , Where 절 (0) | 2023.03.15 |
[QueryDSL] Projection 조회 (0) | 2023.03.15 |
[JPA] Paging / Pagination (Pageable) (0) | 2023.01.28 |
[QueryDSL] 페이징 최적화 (fetchResult Deprecated) (0) | 2023.01.28 |