queryDsl의 exists 이슈에 대해서 알게된 계기가 있었는데, 바로 [우아콘2020] 수십억건에서 QUERYDSL 사용하기 이다.
@Override
public boolean exists(Predicate predicate) {
return createQuery(predicate).fetchCount() > 0;
}
기존 queryDsl에서 exists 함수를 사용하게 되면 내부적으로 count 함수, 정확히는 fetchCount 함수를 실행하여 반환하게 된다는 이야기이다. fetchCount는 exists query를 발생시키지 않고 count query를 발생시킨다.
exists는 첫번째 결과 값을 만나면 반환하는 반면 count 함수는 전체 결과 값을 조회해야 하기 때문에 성능 이슈가 발생하게 된다. 해당 블로그를 보게되면 약 2배 이상 차이가 발생하는 것을 볼 수 있다.
하지만 fetchCount는 queryDsl 5.0 부터 deprecated 되었고, 그렇다면 queryDsl은 현재 어떤 방식으로 exists를 지원하고 있는가! 가 궁금해서 찾아보게 되었다.
Spring Data JPA exists
@Repository
public interface SimpleRepository extends JpaRepository<SimpleEntity, Long> {
boolean existsByName(String name);
}
우선 Spring Data Jpa Repository도 exists함수를 지원한다.
@Test
@Transactional
public void SpringDataJpaRepository의_exist함수를_실행한다() {
// given
SimpleEntity entity = SimpleEntity.builder()
.name("existTest")
.build();
repository.save(entity);
// when
boolean actual = repository.existsByName(entity.getName());
// then
assertTrue(actual);
}
그래서 해당 함수를 호출해보면,
Hibernate:
select
simpleenti0_.id as col_0_0_
from
simple simpleenti0_
where
simpleenti0_.name=? limit ?
다음과 같이 limit를 통해서 함수를 실행하고 있는 것을 알 수 있다.
QueryDsl exists
com.querydsl.core.support.FetchableSubQueryBase
@Override
public BooleanExpression exists() {
QueryMetadata metadata = getMetadata();
if (metadata.getProjection() == null) {
queryMixin.setProjection(Expressions.ONE);
}
return Expressions.predicate(Ops.EXISTS, this);
}
해당 함수는 querydsl에서 지원하는 exists함수이다. 이 함수 외의 다른 exists함수는 지원하지 않는다.
class 이름에서도 볼 수 있듯이 SubQuery에서만 사용 가능한 함수이다.
public boolean existsByName(final String name) {
return from(simple)
.where(simple.name.eq(name))
.fetchFirst() != null;
}
따라서 select에서 exists 함수를 사용하려면 다음과 같이 fetchFirst를 사용해서 구현해야 한다.
Hibernate:
select
simpleenti0_.id as id1_0_,
simpleenti0_.name as name2_0_
from
simple simpleenti0_
where
simpleenti0_.name=? limit ?
fetchFirst를 사용하면 다음과 같이 limit 을 사용하여 select query를 실행하게 된다.
JPQL exsits
그렇다면 JPQL에선 exists를 어떻게 제공하고 있을까?
JPQL의 BNF를 실제로 따라가다 보면..
exists_expression ::= [NOT] EXISTS (subquery)
exists expression은 다음과 같이 subquery라고 명시되어 있다.
그래도 좀 더 확인해보자면
simple_cond_expression ::= comparison_expression | between_expression | like_expression | in_expression | null_comparison_expression | empty_collection_comparison_expression | collection_member_expression | exists_expression
exists expression 은 다음과 같이 simple_cond_expression 에 속해있는데,
conditional_primary ::= simple_cond_expression |(conditional_expression)
conditional_factor ::= [NOT] conditional_primary
conditional_term ::= conditional_factor | conditional_term AND conditional_factor
conditional_expression ::= conditional_term | conditional_expression OR conditional_term
simple_cond_expression은 conditional_expression의 체인으로 사용이 된다.
where_clause ::= WHERE conditional_expression
having_clause ::= HAVING conditional_expression
그리고 conditional_expression은 where절과 having절에만 사용이 가능하다.
그래서 select 에선 exists를 지원하지 않는다는 것을 알 수 있다. 그래서 spring data jpa나 querydsl에서도 select절 exists를 지원하지 못했던 것이다.
열심히 구글링을 해보았으나 jpql 자체에서 select 절의 exists를 지원하지 않는 이유는 찾지 못했다. limit으로 해결할 수 있는 쿼리이기 때문인 것 일까🤔
포스팅에서 사용한 코드는 깃허브에서 볼 수 있다.
'Java > JPA' 카테고리의 다른 글
[QueryDsl] transform GroupBy (0) | 2022.08.12 |
---|---|
[JPA] Spring Jpa Repository의 영속성 컨텍스트 동작방식 (0) | 2022.04.17 |
[JPA] Spring Data에서 save()와 saveAll()의 성능 차이 (0) | 2022.04.04 |