⚔ StckOverflow 이슈와 QueryDSL
Hello에서는 특별한 상황이 아닌 이상, JPA - QueryDSL 방식으로 코드를 통일하고 있다. -> QueryDSL: Type-Safe) 동적 SQL을 작성할 수 있도록 도와주는 Java 기반의 ORM(Query Builder) 가독성이 뛰어나며, 컴파일 시점에 오류 검출 가능
BooleanExpression
은 QueryDSL에서 제공하는 동적 쿼리 조합 기능이다.
ex)
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
public List<User> findUsersByConditions(String name, Integer age) {
QUser user = QUser.user;
JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
BooleanExpression predicate = user.isNotNull(); // 기본 조건 (항상 참)
if (name != null) {
predicate = predicate.and(user.name.eq(name));
}
if (age != null) {
predicate = predicate.and(user.age.gt(age));
}
return queryFactory.selectFrom(user)
.where(predicate)
.fetch();
}
predicate.and(condition)
또는 predicate.or(condition)
을 호출하면,새로운 BooleanExpression 객체를 반환한다.
BooleanExpression expr1 = user.name.like("A%");
BooleanExpression expr2 = expr1.and(user.age.gt(20));
BooleanExpression은 위에서 설명한 바와 같이, Immutable(불변)객체이다. 이 때, 위와같이 and/or 연상을 호출하면, 기존 객체를 변경하는 것이 아닌, 새로운 객체를 만들어 낸다.
해당 연산은 연쇄적으로 새로운 객체를 생성하며, 깊이가 n이 되는 트리(Tree)구조가 형성된다.
or()
/ and()
연산을 할 때마다 기존 객체를 참조하는 새로운 객체가 생성됨.
StckOverflow가 발생하게 되는 것이다.
BooleanBuilder
활용 (QueryDSL 제공)import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.BooleanExpression;
import java.util.List;
public BooleanExpression buildPredicateEfficiently(List<String> patterns) {
QUser user = QUser.user;
BooleanBuilder builder = new BooleanBuilder(); // 동적 조건을 쌓는 도구
for (String pattern : patterns) {
builder.or(user.name.like(pattern + "%")); // 재귀 호출 없이 추가
}
return builder;
}
BooleanBuilder
는 객체를 생성할 때, 내부적으로 BooleanExpression을 List
형태로 관리하게 된다.비교 항목 | BooleanExpression.or() 연속 사용 |
BooleanBuilder 활용 |
---|---|---|
객체 생성 방식 | Immutable(불변) 객체 매번 새로 생성 |
내부적으로 리스트 관리 |
메모리 사용량 | 매우 많음 (새로운 객체 계속 생성) | 상대적으로 적음 |
재귀 깊이 | 깊은 트리 구조 (StackOverflow 발생 가능) | 리스트 구조 (재귀 X) |
성능 | OR 조건이 많을수록 느림 | 상대적으로 빠름 |
권장 여부 | ❌ 비효율적 | ✅ 추천 |
WHERE IN
사용List<String> names = List.of("A", "B", "C");
List<User> users = queryFactory
.selectFrom(user)
.where(user.name.in(names))
.fetch();
비교 항목 | BooleanBuilder (OR) |
WHERE IN |
---|---|---|
SQL 변환 형태 | WHERE name = 'A' OR name = 'B' OR name = 'C' |
WHERE name IN ('A', 'B', 'C') |
실행 계획 (EXPLAIN) | 여러 개의 OR 조건을 평가해야 하므로 느릴 수 있음 |
IN 은 단일 조건으로 평가되므로 더 빠름 |
인덱스 활용 가능성 | OR 연산이 많아지면 인덱스가 제대로 활용되지 않을 가능성 높음 |
B-tree 인덱스를 활용하여 성능이 더 좋을 가능성이 큼 |
데이터 크기 증가 시 성능 | 조건이 많아지면 급격히 성능 저하 | 조건이 많아도 상대적으로 안정적 |
추천 사용 케이스 | 다양한 필드에 동적 조건을 추가할 때 | 단순한 리스트 데이터를 비교할 때 |
하지만!
=
)만을 지원하기 때문에, Like
연산과 같은 패턴 매칭을 이용하기 위해선 BooleanBuilder를 사용해야한다.