[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) {
            // Pageable 객체에서 정렬 조건 추출
            List<OrderSpecifier<?>> orders = getAllOrderSpecifiers(pageable);
    
            // 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();                              // 쿼리 실행
    
            // 검색 결과를 DTO로 변환
            List<ProductResponseDto> content = results.getResults().stream()
                    .map(Product::toResponseDto)
                    .collect(Collectors.toList());
    
            // 총 검색된 데이터 수
            long total = results.getTotal();
    
            // Page 객체로 결과 반환
            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<>();
    
            // Pageable의 정렬 조건을 QueryDSL의 OrderSpecifier로 변환
            if (pageable.getSort() != null) {
                for (Sort.Order sortOrder : pageable.getSort()) {
                    // 정렬 방향 결정 (ASC/DESC)
                    com.querydsl.core.types.Order direction = sortOrder.isAscending() 
                        ? com.querydsl.core.types.Order.ASC 
                        : com.querydsl.core.types.Order.DESC;
    
                    // 정렬 필드에 따른 OrderSpecifier 추가
                    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)를 포함.

    + Recent posts