QueryDsl

QueryDsl에서 in절에 empty list, null이 올 때 어떻게 처리될까?

1. 알고자 하는 것

회사에서 동기가 MyBatis로 작성한 쿼리 중, in절에 대해 empty list가 넘어올 때 Syntax 오류가 발생하여 empty list에 대한 방어 코드 작성 후 다시 배포했었던 상황이 있었다.

 

QueryDsl로 작성한 쿼리에 대해서는 in절에 대해서 별다른 문제가 없었던 것으로 기억해서, QueryDsl의 in절의 내부 동작과 함께 in절에서의 여러 케이스에 대한 결과를 비교해보고자 한다.

  • QueryDsl에서 in query를 사용할 때 List에 empty list가 올 때의 결과
  • QueryDsl에서 in query를 사용할 때 List에 null이 올 때의 결과
  • QueryDsl에서 in query의 내부 동작

 

 

2. 알게된 것

  • QueryDsl에서 in절을 테스트 하기 위해 사용한 쿼리는 다음과 같다. (전체 소스코드는 하단 링크 참조)
public List<String> selectMemberNameList(List<Long> memberIdList) {
    return queryFactory
            .select(member.name)
            .from(member)
            .where(member.id.in(memberIdList)).fetch();
}

 

 

QueryDsl에서 in query를 사용할 때 List에 empty list가 올 때의 결과

  • in절에 사용될 List를 비어있는 list로 주었다.
public List<String> findMemberNameListByEmptyList() {
    return memberRepository.selectMemberNameList(Collections.emptyList());
}
  • 다음과 같이 Exception 없이 정상적으로 비어있는 결과를 반환한다.
// 20230917151622
// http://localhost:8080/members/empty

[
  
]

 

  • 실행된 쿼리는 다음과 같이 where 1=2로 변경되어 수행되었다. 이로 인해 문제 없이 비어있는 결과를 반환할 수 있었다.
Hibernate: select m1_0.name from member m1_0 where 1=2

 

 

 

QueryDsl에서 in query를 사용할 때 List에 null이 올 때의 결과

  • in절에 사용될 List를 null로 주었다.
public List<String> findMemberNameListByNull() {
    return memberRepository.selectMemberNameList(null);
}
  • empty list와는 다르게, NullPointerException이 발생하였다.
java.lang.NullPointerException: Cannot invoke "java.util.Collection.size()" because "right" is null
	at com.querydsl.core.types.dsl.SimpleExpression.in(SimpleExpression.java:192) ~[querydsl-core-5.0.0.jar:na]
  • 객체가 null이어서 Collection Type의 size()를 수행할 수 없어 NullPointerException이 발생하였다.

 

 

QueryDsl에서 in query의 내부 동작

  • in절에 대한 QueryDsl의 내부 동작은 다음과 같이 정의되어 있다.
public BooleanExpression in(Collection<? extends T> right) {
    if (right.size() == 1) {
        return eq(right.iterator().next());
    } else {
        return Expressions.booleanOperation(Ops.IN, mixin, ConstantImpl.create(right));
    }
}
  • in절에 들어오는 list의 사이즈가 1 or else에 따라 동작이 달라짐을 확인할 수 있다.
  • empty list의 경우에는 size() 메서드가 동작하기 때문에 오류가 발생하지 않았다.
  • null의 경우에는 객체가 null이므로 right.size()를 수행할 때 NPE가 발생했다.
  • empty list의 경우, where 1=2로 치환되었는데, 이는 hibernate에서 메서드를 기반으로 SQL을 구성할 때 다음과 같은 동작을 수행하기 때문이다.
public String toFragmentString() {
   if ( values.size() == 0 ) {
      return "1=2";
      ...
   }
  • list의 size가 0일 경우 where 절 뒤에 오는 문자열을 "1=2"로 치환해 모든 조건을 충족하지 못하도록 처리한다.
  • 이로 인해 QueryDsl의 in절에서 empty list에 대해서는 정상적으로 결과를 반환할 수 있었다.

 

 

 

3. 정리

  • QueryDsl에서 in절에 empty list가 들어올 때에는 where 1=2로 치환하여 정상적으로 비어있는 결과가 반환된다.
  • QueryDsl에서 in절에 null이 들어올 때에는 QueryDsl의 in 메서드 내부 동작에서 right.size()을 수행할 때 NPE가 발생한다.
  • QueryDsl을 사용하고, in절에 null이 들어오지 않도록 @RequestBody(required=true)와 같이 input에 대한 validation을 수행하도록 하자.
  • 만약 null이 들어올 수 있도록 해야만 한다면, 다음과 같이 비즈니스 로직에서 적절한 validation을 수행하도록 하자.
public List<String> findMemberNameListByNullValidation(List<Long> memberIdList) {
    if (memberIdList == null) {
        // Custom Exception Throw 등의 Validation 처리
        // 코드 흐름에 맞춘 validation 처리!
         return Collections.emptyList();
    }
    return memberRepository.selectMemberNameList(memberIdList);
}

 

 


전체 소스코드 : https://github.com/HunSeongPark/blog-study/tree/main/querydsl-in-empty-list