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;
}
'JPA & QueryDSL' 카테고리의 다른 글
[JPA] JPA 복합키 Entity Mapping @IdClass / @EmbeddedId (0) | 2025.03.25 |
---|---|
[JPA / Spring] JPQL을 이용해서 Entity가 아닌 DTO를 반환하는 방법 (0) | 2024.05.07 |
[JPA] 연관 관계 매핑 - @ManyToMany (0) | 2023.10.06 |
[JPA] 연관관계 매핑 (1) | 2023.10.06 |
[JPA] 연관관계 매핑 - @OneToOne 일대일 매핑 (0) | 2023.10.06 |
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;
}
'JPA & QueryDSL' 카테고리의 다른 글
[JPA] JPA 복합키 Entity Mapping @IdClass / @EmbeddedId (0) | 2025.03.25 |
---|---|
[JPA / Spring] JPQL을 이용해서 Entity가 아닌 DTO를 반환하는 방법 (0) | 2024.05.07 |
[JPA] 연관 관계 매핑 - @ManyToMany (0) | 2023.10.06 |
[JPA] 연관관계 매핑 (1) | 2023.10.06 |
[JPA] 연관관계 매핑 - @OneToOne 일대일 매핑 (0) | 2023.10.06 |