[241126 TIL]
MSA 학습
MSA 프로젝트 구현
ProductRepositoryImpl 코드 분석
- ProductRepositoryCustom 인터페이스를 구현하여, QueryDSL을 기반으로 복잡한 검색 로직 수행
- 주요 기능- 동적 검색 조건, 정렬, 페이징
코드 전체 구조
@RequiredArgsConstructor
public class ProductRepositoryImpl implements ProductRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public Page<ProductResponseDto> searchProducts(ProductSearchDto searchDto, Pageable pageable) {
List<OrderSpecifier<?>> orders = getAllOrderSpecifiers(pageable);
QueryResults<Product> results = queryFactory
.selectFrom(product)
.where(
nameContains(searchDto.getName()),
descriptionContains(searchDto.getDescription()),
priceBetween(searchDto.getMinPrice(), searchDto.getMaxPrice()),
quantityBetween(searchDto.getMinQuantity(), searchDto.getMaxQuantity())
)
.orderBy(orders.toArray(new OrderSpecifier[0]))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetchResults();
List<ProductResponseDto> content = results.getResults().stream()
.map(Product::toResponseDto)
.collect(Collectors.toList());
long total = results.getTotal();
return new PageImpl<>(content, pageable, total);
}
private BooleanExpression nameContains(String name) {
return name != null ? product.name.containsIgnoreCase(name) : null;
}
private BooleanExpression descriptionContains(String description) {
return description != null ? product.description.containsIgnoreCase(description) : null;
}
private BooleanExpression priceBetween(Double minPrice, Double maxPrice) {
if (minPrice != null && maxPrice != null) {
return product.price.between(minPrice, maxPrice);
} else if (minPrice != null) {
return product.price.goe(minPrice);
} else if (maxPrice != null) {
return product.price.loe(maxPrice);
} else {
return null;
}
}
private BooleanExpression quantityBetween(Integer minQuantity, Integer maxQuantity) {
if (minQuantity != null && maxQuantity != null) {
return product.quantity.between(minQuantity, maxQuantity);
} else if (minQuantity != null) {
return product.quantity.goe(minQuantity);
} else if (maxQuantity != null) {
return product.quantity.loe(maxQuantity);
} else {
return null;
}
}
private List<OrderSpecifier<?>> getAllOrderSpecifiers(Pageable pageable) {
List<OrderSpecifier<?>> orders = new ArrayList<>();
if (pageable.getSort() != null) {
for (Sort.Order sortOrder : pageable.getSort()) {
com.querydsl.core.types.Order direction = sortOrder.isAscending()
? com.querydsl.core.types.Order.ASC
: com.querydsl.core.types.Order.DESC;
switch (sortOrder.getProperty()) {
case "createdAt":
orders.add(new OrderSpecifier<>(direction, product.createdAt));
break;
case "price":
orders.add(new OrderSpecifier<>(direction, product.price));
break;
case "quantity":
orders.add(new OrderSpecifier<>(direction, product.quantity));
break;
default:
break;
}
}
}
return orders;
}
}
searchProducts 메서드
1. 페이징 및 정렬 처리
List<OrderSpecifier<?>> orders = getAllOrderSpecifiers(pageable);
- getAllOrderSpecifiers(pageable):
- QueryDSL의 정렬 객체(OrderSpecifier)를 생성하는 메서드
- Pageable의 정렬 조건에 따라 QueryDSL에서 사용할 정렬 정보를 동적으로 생성
private List<OrderSpecifier<?>> getAllOrderSpecifiers(Pageable pageable) {
List<OrderSpecifier<?>> orders = new ArrayList<>();
if (pageable.getSort() != null) {
for (Sort.Order sortOrder : pageable.getSort()) {
com.querydsl.core.types.Order direction = sortOrder.isAscending()
? com.querydsl.core.types.Order.ASC
: com.querydsl.core.types.Order.DESC;
switch (sortOrder.getProperty()) {
case "createdAt":
orders.add(new OrderSpecifier<>(direction, product.createdAt));
break;
case "price":
orders.add(new OrderSpecifier<>(direction, product.price));
break;
case "quantity":
orders.add(new OrderSpecifier<>(direction, product.quantity));
break;
default:
break;
}
}
}
return orders;
}
- Pageable 객체의 Sort 정보를 기반으로 QueryDSL의 OrderSpecifier 생성
- 정렬 속성(property)에 따라 createdAt, price, quantity 필드의 정렬 조건 추가
2. QueryDSL 검색 쿼리
QueryResults<Product> results = queryFactory
.selectFrom(product)
.where(
nameContains(searchDto.getName()),
descriptionContains(searchDto.getDescription()),
priceBetween(searchDto.getMinPrice(), searchDto.getMaxPrice()),
quantityBetween(searchDto.getMinQuantity(), searchDto.getMaxQuantity())
)
.orderBy(orders.toArray(new OrderSpecifier[0]))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetchResults();
- queryFactory.selectFrom(product)
- QueryDSL로 Product 엔터티를 검색
- where(...)
- 검색 조건을 동적으로 추가
- 각 조건은 BooleanExpression 객체로 반환되며, null인 경우 무시
- orderBy(orders.toArray(...))
- offset(pageable.getOffset()) & limit(pageable.getPageSize())
- 페이징 처리: 요청한 페이지의 시작 위치(offset)와 크기(limit)를 설정
3. 결과 처리
List<ProductResponseDto> content = results.getResults().stream()
.map(Product::toResponseDto)
.collect(Collectors.toList());
long total = results.getTotal();
return new PageImpl<>(content, pageable, total);
- getResults(): 조회된 Product 객체 리스트.
- toResponseDto(): 각 Product 엔터티를 ProductResponseDto로 변환.
- getTotal(): 전체 검색 결과 수.
- PageImpl:
- Spring Data의 Page 구현체로, 결과 데이터(content), 페이징 정보(pageable), 총 데이터 수(total)를 포함.