반응형
Querydsl 사용한 이유
querydsl을 사용하여 동적 정렬을 하면 다음과 같은 장점이 있습니다.
- 컴파일 시점에서 오류가 발생하여 오류를 찾기 쉽다.
- BooleanExpression을 통해 여러 개의 조건문을 가독성 있게 작성할 수 있고 null인 경우 무시하여 조건문을 처리할 수 있다.
- querydsl 쿼리문을 작성할 때 파라미터 바인딩이 자동으로 처리되어 조건문을 작성할 때 편리합니다.
제가 실습한 동적 정렬 기능은 2가지 이상의 정렬을 처리했습니다.
예를 들어 쇼핑몰이라고 생각하겠습니다.
1. "카테고리"를 선택하여 먼저 생성되거나 나중에 생성된 카테고리 순으로 정렬하여 조회합니다.
2. "가격"을 입력받아 해당 가격보다 싸거나 비싼 가격의 상품을 정렬하여 조회합니다.
Controller.class
먼저 컨트롤러 단에서 정렬할 속성과 page, size, sort 정보들을 받을 수 있어야 합니다.
http://localhost:8080/product/search?page=0&size=3&sort=category,asc&sort=price,desc
- 위의 예시 URL 경로를 보면 page, size, sort 정보를 QueryString으로 전달하고 있는 것을 확인할 수 있습니다.
- 위의 3가지 정보를 스프링부트에서는 @PageableDefault, Pageable 을 통해 한꺼번에 처리할 수 있습니다.
@GetMapping("/product/search")
public Page<ProductResponse> searchProduct(
@RequestParam(value = "category", required = false) String category,
@RequestParam(value = "price", required = false) Integer price,
@PageableDefault(size=3) Pageable pageable)
{
ProductVO productVO = ProductVO.of(category, price);
return productService.searchProductByPriceAndCategory(productVO, pageable);
}
Repository.class
1) 전체 Querydsl 쿼리문
public Page<ProductResponse> search(ProductVO productVO, Pageable pageable) {
QProduct product = QProduct.product;
List<ProductResponse> content = jpaQueryFactory
.select(Projections.fields(ProductResponse.class,
product.name,
product.price,
product.category))
.from(product)
.where(priceGoe(productVO.price()),
categoryEq(productVO.category()))
.orderBy(getOrderSpecifiers(pageable.getSort()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
JPAQuery<Long> countQuery = jpaQueryFactory
.select(product.count())
.from(product)
.where(priceGoe(productVO.price()),
categoryEq(productVO.category()));
return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
}
- select 부분에는 조회한 후 반환할 속성명을 작성합니다.
- Projections.fields(DTO.class, ...) 을 통해 클라이언트에 반환할 Response DTO로 값을 주입해 바로 반환할 수 있습니다.
- where 부분에는 여러 개의 조건문을 작성할 수 있습니다.
- (1) 입력받은 가격보다 같거나 비싼 상품들을 조회합니다. (priceGoe 메서드는 아래에서 설명 참고)
- (2) 입력받은 카테고리에 해당하는 상품을 조회합니다. (categoryEq 메서드는 아래에서 설명 참고)
- OrderBy 부분에는 정렬할 기준을 작성합니다. querydsl에서는 OrderSpecifier 형태로 입력을 해주어야 합니다.
- 저는 2개 이상의 정렬 조건을 처리를 getOrderSpeicifers()를 통해 처리했으며 pageable.getSort() 정보를 파라미터로 전달했습니다.
2) BooleanExpression 메서드 - 재사용 가능!!
private BooleanExpression priceGoe(Integer price) {
return price == null ? null : product.price.goe(price);
}
private BooleanExpression categoryEq(String category) {
return isEmpty(category) ? null : product.category.eq(category);
}
- priceGoe()을 통해 product.price.goe(price) 상품의 가격이 파라미터 price보다 크거나 같은 상품을 필터링합니다.
- categoryEq()을 통해 product.category.eq(category) 상품의 카테고리와 파라미터 category가 일치하는지 확인하여 필터링합니다.
- price, category가 null 값인 경우 null을 반환하며, where 절에서는 이를 무시하여 처리합니다.
3) OrderSpecifier 메서드 - 정렬 부분 처리
private OrderSpecifier<?>[] getOrderSpecifiers(Sort sort) {
List<OrderSpecifier<?>> orders = new ArrayList<>();
for (Sort.Order order : sort) {
Order direction = order.isAscending() ? Order.ASC : Order.DESC;
PathBuilder<Product> pathBuilder = new PathBuilder<>(product.getType(), product.getMetadata());
orders.add(new OrderSpecifier<>(direction, pathBuilder.getString(property)));
// System.out.println("order: " + order);
// System.out.println("디렉션: " + direction);
// String property = order.getProperty();
// System.out.println("property: " + property);
}
return orders.toArray(new OrderSpecifier[0]);
}
- Sort에 여러 개의 정렬 조건이 있을 수 있으므로 반복문을 통해 처리합니다.
- 위 코드는 Product 엔티티에 대한 정렬을 처리하기 위해 PathBuilder를 사용하여 엔티티 필드의 경로를 생성하고, 이를 기반으로 정렬 방향에 맞는 OrderSpecifier를 생성합니다.
- order.isAscending()으로 오름차순, 내림차순 정렬인지 확인합니다.
- PathBuilder는 QueryDSL에서 사용하는 클래스로, 특정 엔티티 클래스에 대한 경로를 생성합니다.
- Product 클래스의 경로를 나타내기 위해 PathBuilder <Product>로 제네릭 타입을 지정합니다.
- 생성자의 인자로는 product.getType(), product.getMetadata()로 엔티티의 타입 정보와 메타데이터를 전달합니다.
- OrderSpecifier는 정렬 순서를 나타내는데 사용됩니다.
- direction은 정렬 방향 (Order.ASC or Order.DESC)
- pathBuilder.getString(property)는 pathBuilder에서 property에 해당하는 필드에 대한 경로 표현식을 문자열 타입으로 변환합니다.
반응형
'Spring Framework > Spring boot' 카테고리의 다른 글
Spring Batch JobBuilder와 StepBuilder로 데이터 마이그레이션 - ID 충돌 해결 방법 (0) | 2024.07.21 |
---|---|
[Spring] Spring Batch 이해하기 - Job, Step, Chunk (0) | 2024.07.21 |
Springboot JWT 인증과 권한 처리하기 - 커스텀 어노테이션과 HandlerInterceptor 활용 (0) | 2024.02.10 |
[Spring boot] Spring 커스텀 어노테이션으로 로그인된 사용자 정보 불러오기 - HandlerMethodArgumentResolver, Webconfig (0) | 2024.02.10 |
[Redis] Spring Boot에서 Redis 활용하기: Redis 사용 방법, 연동, 설치 (2) | 2024.01.28 |