ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • querydsl 문제 해결 - 2023-02-16
    querydsl 2023. 2. 21. 16:21
    • 초기 버전의 쿼리
        QClickAggregate qClickAggregate = QClickAggregate.clickAggregate;
        QProduct qProduct = QProduct.product;
        QBrand qBrand = QBrand.brand;
    		QProductCategory qProductCategory = QProductCategory.productCategory;
        QCategory qCategory = QCategory.category;
        JPAQuery<ProductFittingClickHistoryDTO> query = 
    		jpaQueryFactory.select(new QProductClickCountDTO(
                qProduct.id,
                qProduct.thumbnailImageUrl,
                qProduct.nameEn,
                qProduct.nameKo,
                qProduct.productNo,
                qClickAggregate.clickCount.sum().longValue(),
                qBrand.name
            ))
            .from(qProduct)
            .join(qClickAggregate)
            .on(qClickAggregate.productId.eq(qProduct.id))
    				.innerJoin(qProduct.productCategories, qProductCategory)
            .innerJoin(qProductCategory.category, qCategory)
            .innerJoin(qProduct.brand, qBrand)
            .where(qClickAggregate.aggregateDate.between(startDate, endDate));
    		(기타 검색 조건문, 쿼리 생략)
        return QueryDslUtil.getPage(query.groupBy(qProduct.id), PageRequest.of(page, size));
    }
    • 날짜 기간으로 제품을 클릭한 클릭 수를 더해서 정렬한 다음 제품의 정보를 리스트로 보여주는 쿼리였습니다 (검색 조건 카테고리, 상품명 등등)
      • 전체 카테고리도 검색 가능해야 합니다
    • 문제
      • 합계를 더할 때 특정 제품의 특정 날짜 클릭 수가 2배 또는 3배로 더해지던 문제
    • 문제 지점
      • 카테고리가 제품이랑 중간테이블을 낀 1 : n, n : 1의 관계였기 때문에 카테고리가 여러개면 검색이 중복해서 일어나서 더해져서 제품이 가지고 있는 카테고리 개수만큼 클릭수가 더해진 상황
    • 로직 실행 순서
      1. 카테고리로 제품을 검색합니다
      2. 해당 제품들의 아이디를 뽑아 where in 절을 추가 후 집계를 냅니다
      3. 만약 검색조건이 전체 카테고리면 productId가 가지고 있는 카테고리 목록을 다 뽑습니다
      4. 카테고리 목록에 있는 카테고리를 DTO에 넣습니다
    • 고친 쿼리
    • 실행 순서 1번 : 카테고리로 제품을 검색합니다
    public List<Long> getProductIdsByCategoryId(Long categoryId) {
            QProduct qProduct = QProduct.product;
            QProductCategory qProductCategory = QProductCategory.productCategory;
            QCategory qCategory = QCategory.category;
    
            return jpaQueryFactory.select(new QIdDTO(qProduct.id))
                .from(qProduct)
                .innerJoin(qProduct.productCategories, qProductCategory)
                .innerJoin(qProductCategory.category, qCategory)
                .where(qCategory.id.eq(categoryId))
                .fetch().stream().map(IdDTO::getId).collect(Collectors.toList());
    }
    
    • 실행 순서 2, 3번 : 해당 제품들의 아이디를 뽑아 where in 절을 추가 후 집계를 냅니다
    	QClickAggregate qClickAggregate = QClickAggregate.clickAggregate;
            QProduct qProduct = QProduct.product;
            QBrand qBrand = QBrand.brand;
            JPAQuery<ProductFittingClickHistoryDTO> query = 
    				jpaQueryFactory.select(new QProductClickCountDTO(
                    qProduct.id,
                    qProduct.thumbnailImageUrl,
                    qProduct.nameEn,
                    qProduct.nameKo,
                    qProduct.productNo,
                    qClickAggregate .clickCount.sum().longValue(),
                    qBrand.name
                ))
                .from(qProduct)
                .join(qBatchProductFittingHistory)
                .on(qClickAggregate .productId.eq(qProduct.id))
                .innerJoin(qProduct.brand, qBrand)
                .where(qClickAggregate .aggregateDate.between(startDate, endDate));
                **if(categoryId != null && categoryId > 0L) {
                    List<Long> productIds = this.getProductIdsByCategoryId(categoryId);
                    query = query.where(qProduct.id.in(productIds));
                }**
    	return QueryDslUtil.getPage(query.groupBy(qProduct.id), PageRequest.of(page, size));
    • 실행 순서 4 : 만약 검색조건이 전체 카테고리면 productId가 가지고 있는 카테고리 목록을 다 뽑습니다
    	QProduct qProduct = QProduct.product;
            QProductCategory qProductCategory = QProductCategory.productCategory;
            QCategory qCategory = QCategory.category;
    
            return jpaQueryFactory.select(new QIdNameDTO(qProduct.id, qCategory.name))
                .distinct()
                .from(qProduct)
                .innerJoin(qProduct.productCategories, qProductCategory)
                .innerJoin(qProductCategory.category, qCategory)
                .where(qProduct.id.in(productIds))
                .fetch();
    • 실행 순서 5 : 카테고리를 DTO에 넣습니다
    List<IdNameDTO> categoryNameAndProductIds = 
    		productDetailClickAggregateQuery.getCategoryIdNamesByProductIds(productIds);
    
    Map<Long, ProductFittingClickHistoryDTO> map = result
        .getContent()
        .stream()
        .collect(Collectors.toMap(ProductClickCountDTO::getProductId, a -> a));
    
    for (IdNameDTO idNameDTO: categoryNameAndProductIds
    ) {
        ProductFittingClickHistoryDTO productFittingClickHistoryDTO = 
    				map.get(idNameDTO.getId());
        productFittingClickHistoryDTO.addCategories(idNameDTO.getName());
    }
    • querydsl을 쓰면서 고려했던 점들
      • fetch join + page를 쓰는 것을 피했습니다 
      • fetch join을 안하고 연결 객체를 가져다가 쓰는 것을 피했습니다 (n + 1)

     

    'querydsl' 카테고리의 다른 글

    querydsl에서 mysql 메서드 사용하기  (1) 2023.03.20
    querydsl page 처리 with group by  (0) 2023.03.02
Designed by Tistory.