JPA & QueryDSL

[QueryDSL] BooleanExpression을 이용한 WHERE 조건 표현식

HSRyuuu 2025. 3. 7. 18:53

QueryDSL은 JPA와 함께 사용하여, 복잡한 동적 쿼리, Join 쿼리 등을 최적화해 준다.

이중 BooleanExpression을 주로 사용하여 where 조건절을 세팅한다.

이 글은 Boolean expression을 사용하는 여러 상황을 정리하고, BooleanExpression이 제공하는 메서드를 살펴본다.

Version

# version
spring-boot: 3.4.3
JPA: 3.1.0
querydsl: 5.0.0
# build.gradle
dependencies {
    implementation "com.querydsl:querydsl-jpa:5.0.0:jakarta"
    annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
    annotationProcessor "jakarta.annotation:jakarta.annotation-api"
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

 

BooleanExpression.class


package com.querydsl.core.types.dsl

 


BooleanExpression.and()

여러 개의 BooleanExpression을 and 조건으로 연결한다.

BooleanExpression이 null일 경우에 자동으로 조건절에서 제거된다.

public BooleanExpression and(@Nullable Predicate right) {
    right = (Predicate)ExpressionUtils.extract(right);
    return (BooleanExpression)(right != null ? Expressions.booleanOperation(Ops.AND, new Expression[]{this.mixin, right}) : this);
}

 

example 1)

 // 검색어 조건
BooleanExpression searchWordCondition = this.getSearchWordCondition(filter.getSearchField(), filter.getSearchKeyword());
// 기간 조건
BooleanExpression periodCondition = this.getPeriodCondition(filter.getPeriodField(), filter.getPeriodStart(), filter.getPeriodEnd());
// 공개여부 조건
BooleanExpression accessRightsCondition = this.getAccessRightsCondition(filter.getAccessRights());
// and 조건으로 연결
BooleanExpression totalCondition = searchWordCondition
                			.and(periodCondition)
                			.and(accessRightsCondition);

이렇게 하면,첫 번째 조건 (searchWordCondition)이 null인 경우 에러가 발생한다.

따라서 아래와 같이 개선할 수 있다.

example 2)

이렇게 하면 where 1=1 and ... 처럼 동작하여 null일 경우 에러를 방지할 수 있다.

BooleanExpression totalCondition = 
                Expressions.TRUE
                .and(searchWordCondition)
                .and(periodCondition)
                .and(accessRightsCondition);

example 3)

여러 개의 조건을 where 절에 직접 넣을 경우 null 일 경우에도 문제가 없다. (쉼표로 구분하여 넣을 수 있다.)

개수가 많이 안을 경우 이 방법을 추천한다.

List<DataResource> fetchResult = 
                factory.selectFrom(dataResource)
                .where(searchWordCondition, periodCondition, accessRightsCondition)
                .fetch();

 

BooleanExpression.or()

여러개의 BooleanExpression을 or 조건으로 연결한다.

BooleanExpression이 null일 경우에 자동으로 조건절에서 제거된다. 

or 조건의 경우에는 null.or(null)의 경우에도 문제없이 null을 반환하여 무시하게 된다.

public BooleanExpression or(@Nullable Predicate right) {
    right = (Predicate)ExpressionUtils.extract(right);
    return (BooleanExpression)(right != null ? Expressions.booleanOperation(Ops.OR, new Expression[]{this.mixin, right}) : this);
}

example)

searchField == ALL일 경우, TITLE, CREATOR, PUBLISHER 조건을 or로 연결한다.

private BooleanExpression getSearchWordCondition(DataResourceSearchField searchField, String keyword) {
    if (keyword == null || keyword.isEmpty()) {
        return null;
    }
    switch (searchField) {
        case TITLE -> {/*생략*/}
        case CREATOR -> {/*생략*/}
        case PUBLISHER -> {/*생략*/}
        case ALL -> {
            return QueryDslConditionUtils.getLikeCondition(dataResource.title, keyword)
                    .or(QueryDslConditionUtils.getLikeCondition(dataResource.creator, keyword))
                    .or(QueryDslConditionUtils.getLikeCondition(dataOrganization.name, keyword));
        }
        default -> {
            return null;
        }
    }
}
//QueryDslConditionUtils.class
public static BooleanExpression getLikeCondition(StringPath column, String searchWord) {
    if (searchWord == null || searchWord.isEmpty() || searchWord.equals("%%")) {
        return null;
    }
    return searchWord.contains("%") ?
            column.likeIgnoreCase(searchWord)
            : column.likeIgnoreCase("%" + searchWord + "%");
}

 

BooleanExpression.not()

하나의 BooleanExpression을 NOT 연산으로 논리 부정을 수행한다.

즉 where not (condition)과 같은 역할을 한다.

BooleanExpression이 NULL일 경우 NULL.not()을 수행하면 NPE가 발생하니 주의해야 한다.

public BooleanExpression not() {
    if (this.not == null) {
        this.not = Expressions.booleanOperation(Ops.NOT, new Expression[]{this});
    }

    return this.not;
}

 

BooleanExpression.coalesce()

BooleanExpression이 NULL일 경우 대체값(기본값)을 설정한다.

매개변수가 여러 개일 경우, null이 아닌 첫 번째 값을 사용한다.

// BooleanExpression이 null일 경우 expr을 사용
public BooleanExpression coalesce(Expression<Boolean> expr) {
    Coalesce<Boolean> coalesce = new Coalesce(this.getType(), this.mixin);
    coalesce.add(expr);
    return coalesce.asBoolean();
}
// BooleanExpression이 null일 경우 exprs 중 첫번재로 null이 아닌 값을 사용
public BooleanExpression coalesce(Expression<?>... exprs) {
    Coalesce<Boolean> coalesce = new Coalesce(this.getType(), this.mixin);

    for(Expression expr : exprs) {
        coalesce.add(expr);
    }

    return coalesce.asBoolean();
}
// BooleanExpression이 null일 경우 Boolean인 arg 값을 BooleanExpression으로 변환하여 사용한다.
public BooleanExpression coalesce(Boolean arg) {
    Coalesce<Boolean> coalesce = new Coalesce(this.getType(), this.mixin);
    coalesce.add(arg);
    return coalesce.asBoolean();
}

example)

// 검색어 조건
BooleanExpression searchWordCondition = this.getSearchWordCondition(filter.getSearchField(), filter.getSearchKeyword());
// 기간 조건
BooleanExpression periodCondition = this.getPeriodCondition(filter.getPeriodField(), filter.getPeriodStart(), filter.getPeriodEnd());
// 공개여부 조건
BooleanExpression accessRightsCondition = this.getAccessRightsCondition(filter.getAccessRights());
// and 조건으로 연결
BooleanExpression totalCondition = searchWordCondition.coalesce(Expressions.TRUE)
        .and(periodCondition)
        .and(accessRightsCondition);

 

이외에도 여러가지 기능과 메서드를 지원하니 com.querydsl.core.types.dsl.BooleanExpression.class를 찾아보자.


Code examples


public List<DataResource> searchDataResource(DataResourceSearchFilter filter, Pageable pageable) {
    // 삭제된 것 제외
    BooleanExpression exceptDeleted = dataResource.deleteApprv.isNull();
    // 수집 방법 조건
    BooleanExpression collectPolicyCondition = dataResource.hasPolicy.eq("Y");
    // 데이터 유형 조건
    BooleanExpression dataTypeCondition = dataResource.type.eq("db");
    // 공개여부 조건
    BooleanExpression accessRightsCondition = dataResource.accessRights.eq("Y");
    // not deleted
    BooleanExpression notDeletedCondition = dataResource.deleteApprv.isNull();
    // total condition
    BooleanExpression whereConditions = notDeletedCondition.coalesce(Expressions.TRUE)
            .and(exceptDeleted)
            .and(collectPolicyCondition)
            .and(dataTypeCondition)
            .and(accessRightsCondition);
            
    // 조회 execute
    List<DataResource> dataResources = factory.selectFrom(dataResource)
            .where(whereConditions)
            .fetch();
    return dataResources;
}
반응형