1. BooleanExpression 과 BooleanBuilder
위 두 기능은 QueryDSL에서 조건문을 구성할 때 사용한다.
1-1. BooleanExpression
BooleanExpression은 불변 객체로 한 번 생성되면 그 상태가 변경되지 않는다.
BooleanExpression은 개별 조건을 표현하며, 이러한 조건들을 연결하여 복잡한 쿼리를 구성할 수 있다.
비교, 논리, 연산, 수학적 조건 등 다양한 형태의 조건을 나타낼 수 있고, 새로운 BooleanExpression를 반환한다.
예시
BooleanExpression condition1 = qProduct.price.gt(100);
BooleanExpression condition2 = qProduct.category.eq("Books");
BooleanExpression combinedCondition = condition1.and(condition2);
combinedCondition은 새로운 BooleanExpression 객체이며, 원래의 condition1과 condition2는 변경되지 않는다.
1-2 BooleanBuilder
BooleanBuilder는 가변 객체로 하나 이상의 BooleanExpression을 논리적으로 조합하여 복잡한 조건을 동적으로 구성할 수 있다. 또 조건이 실행 도중에 추가되거나 변경될 수 있다.
BooleanExpression는 내부적으로 하나 이상의 BooleanExpression을 관리하면서, 필요에 따라 AND / OR 등의 논리 연산자로 연결하여 동적으로 조건을 구성한다.
이는 동적 쿼리를 구성할 때 유용하며, 실행 시점에 조건을 추가하거나 변경하는 데에 사용된다.
차이점
1. 변경가능성
- BooleanExpression : 불변 객체로, 새 조건이 필요할 때마다 새 객체를 생성한다.
- BooleanBuilder : 가변 객체로, 조건을 동적으로 추가하며 내부 상태가 변경된다.
2. 유연성
- BooleanExpression : 조건을 명시적으로 연결해야 하며, 복잡한 동적 구조 조건 구성에는 제한적이다.
- BooleanBuilder : 매우 유연하며, 실행 시점에 조건을 추가하거나 제거하는 것이 간단하다. 따라서 사용자의 입력과 같이 변화하는 조건에 특히 유용하다.
3. 사용 케이스
- BooleanExpression : 조건이 상대적으로 고정적이거나, 조건의 구조가 간단할 때 적합하다.
- BooleanBuilder : 조건이 실행 중에 결정되거나 사용자의 입력과 같이 다양한 요소에 따라 달라질 때 적합하다.
즉, BooleanExpression은 단일 조건, BooleanBuilder는 여러 BooleanExpression 을 조합하여 복합적인 조건을 만들 때 사용된다. BooleanExpression으로도 조건식을 사용할 수 있지만, BooleanBuilder는 이러한 조건들을 조합하여 다양하고 복잡한 쿼리를 작성할 수 있다.
실제 프로젝트 때 사용한 예시
@Override
public List<Product> findProductListByFilterOptions(List<String> searchKeyword,
List<String> categoryNo,
List<String> subCategoryName,
List<String> mainTypes,
List<String> types,
List<String> alcoholLevels,
List<String> prices) {
QProduct qProduct = QProduct.product;
QLikeProduct qLikeProduct = QLikeProduct.likeProduct;
QReview qReview = QReview.review;
BooleanBuilder finalBuilder = new BooleanBuilder();
// 검색어 필터
if (searchKeyword != null && !searchKeyword.isEmpty()) {
BooleanBuilder keywordBuilder = new BooleanBuilder();
searchKeyword.forEach(keyword -> {
if (keyword != null) {
keywordBuilder.or(qProduct.name.containsIgnoreCase(keyword));
}
});
finalBuilder.and(keywordBuilder);
}
// 카테고리 번호 필터
if (categoryNo != null && !categoryNo.isEmpty()) {
BooleanBuilder categoryBuilder = new BooleanBuilder();
try {
Integer categoryNumber = categoryNo.get(0) != null ? Integer.valueOf(categoryNo.get(0)) : null;
if (categoryNumber != null) {
List<String> subCategories = subCategoryService.findCategoryNameByCategoryNo(categoryNumber);
if (subCategories != null && !subCategories.isEmpty()) {
subCategories.forEach(subCategory -> {
if (subCategory != null) {
categoryBuilder.or(qProduct.keyword.contains(subCategory));
}
});
} else {
// 서브 카테고리가 비어있는 경우, 주 카테고리만으로 검색 진행
categoryBuilder.or(qProduct.productId.eq(String.valueOf(categoryNumber)));
}
finalBuilder.and(categoryBuilder);
}
} catch (NumberFormatException e) {
log.error("Invalid category number format", e);
}
}
// 하위 카테고리 이름 필터
if (subCategoryName != null && !subCategoryName.isEmpty()) {
BooleanBuilder subCategoryBuilder = new BooleanBuilder();
subCategoryName.forEach(keyword -> {
if (keyword != null) {
subCategoryBuilder.or(qProduct.keyword.contains(keyword));
}
});
finalBuilder.and(subCategoryBuilder);
}
// 주종 필터
if (mainTypes != null && !mainTypes.isEmpty()) {
BooleanBuilder mainTypeBuilder = new BooleanBuilder();
mainTypes.forEach(mainType -> mainTypeBuilder.or(qProduct.productId.eq(mainType)));
finalBuilder.and(mainTypeBuilder);
}
// 유형 필터
if (types != null && !types.isEmpty()) {
BooleanBuilder typeBuilder = new BooleanBuilder();
types.forEach(type -> typeBuilder.or(qProduct.type.eq(type)));
finalBuilder.and(typeBuilder);
}
// 알코올 도수 필터
if (alcoholLevels != null && !alcoholLevels.isEmpty()) {
BooleanBuilder alcoholLevelBuilder = new BooleanBuilder();
alcoholLevels.forEach(alcoholLevel -> alcoholLevelBuilder.or(createAlcoholFilter(alcoholLevel)));
finalBuilder.and(alcoholLevelBuilder);
}
// 가격 필터
if (prices != null && !prices.isEmpty()) {
BooleanBuilder priceRangeBuilder = new BooleanBuilder();
prices.forEach(priceRange -> priceRangeBuilder.or(createPriceFilter(priceRange)));
finalBuilder.and(priceRangeBuilder);
}
List<Product> productList = factory.select(qProduct)
.from(qProduct)
.where(finalBuilder)
.fetch();
return new ArrayList<>(productList);
}
2. OrderSpecifier
QueryDSL에서 정렬 순서를 지정할 때 사용되는 클래스이다.
이를 통해 쿼르 결과의 정렬 방식을 제어할 수 있으며, 쿼리 내에서 정렬 순서를 지정할 수 있다.
OrderSpecifier를 사용하면 결과를 오름차순 또는 내림차순으로 정렬할 수 있으며, 다중 필드 정렬 등 복잡한 정렬 기준도 쉽게 구현할 수 있다.
사용방법
OrderSpecifier 객체를 생성할 때는 정렬 방향 ( Order.ASC 또는 Order.DESC )과 정렬할 필드를 지정해야 한다.
이 필드는 일반적으로 Q 클래스 속성을 사용한다.
예시
QUser user = QUser.user;
// 이름으로 오름차순 정렬
OrderSpecifier<String> orderByName = new OrderSpecifier<>(Order.ASC, user.name);
// 나이로 내림차순 정렬 (이름이 같을 경우)
OrderSpecifier<Integer> orderByAgeDesc = new OrderSpecifier<>(Order.DESC, user.age);
List<User> users = new JPAQuery<>(entityManager)
.select(user)
.from(user)
.where(user.isActive.isTrue())
.orderBy(orderByName, orderByAgeDesc)
.fetch();
추가 기능
- null처리 : QueryDSL은 null 값을 어떻게 처리할 지 지정할 수 있는 옵션을 제공한다.
nullsFirst( ) 또는 nullsLast( )를 사용하여 정렬시 null 값을 처음이나 마지막에 위치하도록 할 수 있다.
- 사용자 정의 비교자 : 사용자가 직접 비교 로직을 정의하여 정렬 기준으로 사용할 수도 있다.
'공부할 것 !' 카테고리의 다른 글
HTTP 메서드의 이해와 활용 (0) | 2024.05.12 |
---|---|
NoSQL (0) | 2024.05.08 |
아스키 코드 (0) | 2024.05.08 |
사용자 정보 저장 및 사용 방법 ( 쿠키, 세션, 스프링시큐리티 ) (0) | 2024.04.21 |
[JPA] QueryDSL (0) | 2024.04.13 |