
개요
Query DSL에 대한 개념과 기능들을 공부하고 프로젝트에 필터링 검색 기능으로 적용해보던 중, 여러 값에 대한 존재 여부를 조건절로 걸어야 했다. 간단할 줄 알았지만 추가적인 공부가 필요했고, 다행히 Query DSL에서는 해당 이슈 해결을 위한 API를 제공해주고 있었다. 실제로 다른 사이트에서도 많이 봐온 기능이라 이렇게 글로 정리해본다.
요구사항

필터링 검색 기능 중 '어떤 모임에 가고 싶으세요' 요구사항을 다음과 같은 경우로 나누어 비교하려 한다
- 단일 태그를 만족하는 모임을 필터링 검색할 수 있다 - 단일 값 존재 여부 확인
- 여러 태그 중 일부 만족하는 모임을 필터링 검색할 수 있다 - 여러 값 존재 여부 확인
2번 요구사항에 대해 추가적으로 설명하자면, 검색 조건이 '외향적인', '친구끼리', '소통'이라면
1. 외향적인, 술꾼들
2. 내향적인, 친구끼리, 소통
3. 외향적인, 시끄러운, 소통
.
.
다음과 같이 모임이 필터링 검색 될 수 있을 것이다.
인프런 강의 필터링 검색을 예로 들 수 있겠다.

Entity
@Entity
@Table(name = "party")
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class PartyEntity extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
.
.
//모임 생성 시 설정한 태그
@BatchSize(size = PartyTag.size)
@ElementCollection(fetch = FetchType.LAZY)
private List<PartyTag> partyTags = new ArrayList<>();
.
.
}
모임에 대한 엔티티이며, 불필요한 정보들은 생략하고, 현재 알아볼 요구사항에 대한 필드만 가져왔다.
단일 값 존재 여부 확인
태그 하나에 대한 존재 여부를 확인하는 코드이다.
public class PartyRepositoryImpl implements PartyRepositoryCustom {
private final JPAQueryFactory queryFactory;
public PartyRepositoryImpl(EntityManager em) {
queryFactory = new JPAQueryFactory(em);
}
@Override
public List<PartyEntity> findAllBySearch(Pageable pageable, PartySearchCondition searchCondition) {
return queryFactory
.selectFrom(partyEntity)
.where(
.
.
//어떤 모임에 가고 싶으세요?
containsTag(searchCondition.getPartyTag())
)
.fetch();
}
//단일 태그 존재 여부 조건
private BooleanExpression containsTag(PartyTag partyTag) {
return partyEntity.partyTags.contains(partyTag);
}
}
검색 조건으로 넘어온 태그가 하나이기에, contains() 연산을 한 BooleanExpression으로 반환해주기만 하면 된다.
Query DSL을 사용해 본 분들이라면, 해당 조건절을 BooleanExpression로 작성하는 것은 크게 어렵지 않을 것이다.
여러 값 존재 여부 확인
문제는 이 부분이다. 요구사항을 다시 보자
여러 태그 중 일부 만족하는 모임을 필터링 검색할 수 있다
요구사항 조건을 처리하기 위해 다음의 과정이 필요하다.
- 조건으로 들어온 태그들 각각에 대한 존재여부를 BooleanExpression[] 타입으로 갖고있어야 한다.
- BooleanExpression[] 요소들을 각각 OR 연산하여 BooleanExpression으로 정리한다.
- where절 조건으로 설정한다.
위의 과정을 적용한 코드는 다음과 같다.
public class PartyRepositoryImpl implements PartyRepositoryCustom {
private final JPAQueryFactory queryFactory;
public PartyRepositoryImpl(EntityManager em) {
queryFactory = new JPAQueryFactory(em);
}
@Override
public List<PartyEntity> findAllBySearch(Pageable pageable, PartySearchCondition searchCondition) {
return queryFactory
.selectFrom(partyEntity)
.where(
.
.
//어떤 모임에 가고 싶으세요?
containsAnyTags(searchCondition.getPartyTagList())
)
.fetch();
}
//여러 태그 존재 여부 조건
private BooleanExpression containsAnyTags(List<PartyTag> partyTagList) {
if (partyTagList.isEmpty()) {
return null;
}
//필터링 조건으로 들어온 여러 태그에 대해 BooleanExpression 변환
BooleanExpression[] booleanExpressions = partyTagList.stream()
.map(this::containsTag)
.toArray(BooleanExpression[]::new);
//BooleanExpression들을 OR 연산
BooleanExpression result = null;
for(BooleanExpression b : booleanExpressions){
if(result==null){
result = b;
}
else{
result = result.or(b);
}
}
}
//단일 태그 존재 여부 조건
private BooleanExpression containsTag(PartyTag partyTag) {
return partyEntity.partyTags.contains(partyTag);
}
}
조금 더 알아본 결과, Query DSL에서는 Expression 타입을 편하게 다루게 해주는 Expressions 클래스가 있었으며, BooleanExpression[]에 대한 OR, AND연산 기능도 지원해주고 있었다.
public class PartyRepositoryImpl implements PartyRepositoryCustom {
private final JPAQueryFactory queryFactory;
public PartyRepositoryImpl(EntityManager em) {
queryFactory = new JPAQueryFactory(em);
}
@Override
public List<PartyEntity> findAllBySearch(Pageable pageable, PartySearchCondition searchCondition) {
return queryFactory
.selectFrom(partyEntity)
.where(
.
.
//어떤 모임에 가고 싶으세요?
containsAnyTags(searchCondition.getPartyTagList())
)
.fetch();
}
//여러 태그 존재 여부 조건
private BooleanExpression containsAnyTags(List<PartyTag> partyTagList) {
if (partyTagList.isEmpty()) {
return null;
}
//필터링 조건으로 들어온 여러 태그에 대해 BooleanExpression 변환
BooleanExpression[] booleanExpressions = partyTagList.stream()
.map(this::containsTag)
.toArray(BooleanExpression[]::new);
//BooleanExpression들을 OR 연산 -> Expressions 사용하여 코드 단축
return Expressions.anyOf(booleanExpressions);
}
//단일 태그 존재 여부 조건
private BooleanExpression containsTag(PartyTag partyTag) {
return partyEntity.partyTags.contains(partyTag);
}
}
요구사항이 '여러 태그를 모두 만족하는 모임을 필터링 검색할 수 있다' 로 변경된다면,
'Expressions.allOf()' 로 변경하면 AND 연산이 될 것이다.
정리
Expression을 구현한 DateExpression, TimeExpression, NumberExpression 등 여러 타입이 있으며, 이 타입들 또한 편리하게 사용하도록 Expressions는 여러 기능들을 제공하고 있다.
하지만 아직 깊게 기술을 사용해보지 못한 탓인지 해당 타입들과 관련된 기능들에 대해서는 필요성을 느끼지 못하였다.
BooleanExpression과 소개한 Expressions 기능들만 하더라도 정말 강력하고 자주 써먹을 것 같다!
'JPA' 카테고리의 다른 글
| [JPA] saveAll() 문제점과 JDBC를 통한 해결 (0) | 2026.01.14 |
|---|---|
| [JPA] @SQLRestriction으로 soft delete 구현하기 (0) | 2025.12.21 |
| [JPA] 프록시 객체와 OneToOne 양방향 관계 이슈 (0) | 2025.12.21 |
| [JPA] 영속성 컨텍스트와 flush 시점 (0) | 2025.12.20 |
| [JPA] 테스트를 통해 알아본 JPA와 영속성 컨텍스트 (0) | 2024.03.15 |