diff --git a/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/dto/RankingAggregation.java b/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/dto/RankingAggregation.java index b5b61a2a6..057179365 100644 --- a/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/dto/RankingAggregation.java +++ b/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/dto/RankingAggregation.java @@ -3,6 +3,7 @@ import java.math.BigDecimal; import com.loopers.batch.job.ranking.support.ScoreCalculator; +import com.loopers.domain.metrics.ProductMetricsAggregation; import lombok.Getter; @@ -38,30 +39,31 @@ private RankingAggregation(Long productId, long viewCount, long likeCount, /** * DB 집계 결과로부터 RankingAggregation을 생성합니다. * - * @param row DB 집계 쿼리 결과 (Object[] 형태) + * @param metrics 상품 메트릭 집계 결과 DTO * @param calculator 점수 계산기 * @return 생성된 RankingAggregation 객체 - * @throws IllegalArgumentException row가 null이거나 형식이 잘못된 경우 + * @throws IllegalArgumentException metrics가 null인 경우 */ - public static RankingAggregation from(Object[] row, ScoreCalculator calculator) { - if (row == null || row.length < 4) { - throw new IllegalArgumentException("집계 결과 배열이 null이거나 길이가 부족합니다."); + public static RankingAggregation from(ProductMetricsAggregation metrics, ScoreCalculator calculator) { + if (metrics == null) { + throw new IllegalArgumentException("집계 결과(metrics)가 null입니다."); } - try { - Long productId = (Long) row[0]; - long viewCount = ((Number) row[1]).longValue(); - long likeCount = ((Number) row[2]).longValue(); - long salesCount = ((Number) row[3]).longValue(); - long orderCount = ((Number) row[4]).longValue(); - BigDecimal totalSalesAmount = (BigDecimal) row[5]; + long totalScore = calculator.calculate( + metrics.viewCount(), + metrics.likeCount(), + metrics.totalSalesAmount() + ); - long totalScore = calculator.calculate(viewCount, likeCount, totalSalesAmount); - - return new RankingAggregation(productId, viewCount, likeCount, salesCount, orderCount, totalSalesAmount, totalScore); - } catch (ClassCastException | NullPointerException e) { - throw new IllegalArgumentException("집계 결과 데이터 형식이 올바르지 않습니다.", e); - } + return new RankingAggregation( + metrics.productId(), + metrics.viewCount(), + metrics.likeCount(), + metrics.salesCount(), + metrics.orderCount(), + metrics.totalSalesAmount(), + totalScore + ); } /** diff --git a/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/reader/AbstractMetricsReader.java b/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/reader/AbstractMetricsReader.java index f0da39d5f..f500a1004 100644 --- a/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/reader/AbstractMetricsReader.java +++ b/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/reader/AbstractMetricsReader.java @@ -8,6 +8,7 @@ import com.loopers.batch.job.ranking.dto.RankingAggregation; import com.loopers.batch.job.ranking.support.RankingAggregator; +import com.loopers.domain.metrics.ProductMetricsAggregation; import com.loopers.domain.metrics.ProductMetricsRepository; import lombok.extern.slf4j.Slf4j; @@ -45,13 +46,16 @@ private void initializeIterator() { try { // 1. 기간 파싱 (추상 메서드 호출) LocalDate[] dateRange = parseDateRange(); + if (dateRange == null || dateRange.length != 2) { + throw new IllegalStateException("parseDateRange()는 정확히 2개의 날짜를 반환해야 합니다."); + } LocalDate startDate = dateRange[0]; LocalDate endDate = dateRange[1]; log.info("집계 기간: {} ~ {}", startDate, endDate); // 2. DB에서 집계 쿼리 실행 - List aggregationResults = productMetricsRepository.aggregateByDateRange(startDate, endDate); + List aggregationResults = productMetricsRepository.aggregateByDateRange(startDate, endDate); log.info("집계 대상 상품 수: {}", aggregationResults.size()); // 3. 랭킹 처리 (정렬 + TOP 100 + 순위 부여) diff --git a/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/support/RankingAggregator.java b/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/support/RankingAggregator.java index 36b809fe2..8fffd5df0 100644 --- a/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/support/RankingAggregator.java +++ b/apps/commerce-batch/src/main/java/com/loopers/batch/job/ranking/support/RankingAggregator.java @@ -3,6 +3,7 @@ import java.util.Comparator; import java.util.List; +import com.loopers.domain.metrics.ProductMetricsAggregation; import org.springframework.stereotype.Component; import com.loopers.batch.job.ranking.dto.RankingAggregation; @@ -29,14 +30,14 @@ public class RankingAggregator { * @param aggregationResults DB 집계 쿼리 결과 목록 * @return TOP 100 랭킹 목록 (순위 부여 완료) */ - public List processRankings(List aggregationResults) { + public List processRankings(List aggregationResults) { if (aggregationResults == null || aggregationResults.isEmpty()) { return List.of(); } // 1. DTO 변환 + 점수 계산 List aggregations = aggregationResults.stream() - .map(row -> RankingAggregation.from(row, scoreCalculator)) + .map(metrics -> RankingAggregation.from(metrics, scoreCalculator)) .toList(); // 2. 점수 기준 내림차순 정렬 + TOP 100 필터링 diff --git a/apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java b/apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java index d49e324a6..a173e83b9 100644 --- a/apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java +++ b/apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java @@ -3,6 +3,7 @@ import java.time.LocalDate; import java.util.List; +import com.loopers.domain.metrics.ProductMetricsAggregation; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -22,20 +23,21 @@ public interface ProductMetricsJpaRepository extends JpaRepository aggregateByDateRange( + List aggregateByDateRange( @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); diff --git a/apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java b/apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java index 5d914cc34..78ed15b61 100644 --- a/apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java +++ b/apps/commerce-batch/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Optional; +import com.loopers.domain.metrics.ProductMetricsAggregation; import org.springframework.stereotype.Repository; import com.loopers.domain.metrics.ProductMetricsEntity; @@ -44,7 +45,7 @@ public List findByMetricDateBetween(LocalDate startDate, L } @Override - public List aggregateByDateRange(LocalDate startDate, LocalDate endDate) { + public List aggregateByDateRange(LocalDate startDate, LocalDate endDate) { return jpaRepository.aggregateByDateRange(startDate, endDate); } } diff --git a/apps/commerce-batch/src/test/java/com/loopers/batch/job/ranking/dto/RankingAggregationUnitTest.java b/apps/commerce-batch/src/test/java/com/loopers/batch/job/ranking/dto/RankingAggregationUnitTest.java index 9f6270804..16f463c14 100644 --- a/apps/commerce-batch/src/test/java/com/loopers/batch/job/ranking/dto/RankingAggregationUnitTest.java +++ b/apps/commerce-batch/src/test/java/com/loopers/batch/job/ranking/dto/RankingAggregationUnitTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import com.loopers.batch.job.ranking.support.ScoreCalculator; +import com.loopers.domain.metrics.ProductMetricsAggregation; @DisplayName("RankingAggregation 단위 테스트") class RankingAggregationUnitTest { @@ -20,10 +21,12 @@ class 집계_결과로부터_생성 { @DisplayName("유효한 집계 결과로부터 객체를 생성한다") void should_create_from_valid_aggregation_result() { // given - Object[] row = {1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000)}; // productId, view, like, sales, order, amount + ProductMetricsAggregation metrics = new ProductMetricsAggregation( + 1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000) + ); // when - RankingAggregation aggregation = RankingAggregation.from(row, calculator); + RankingAggregation aggregation = RankingAggregation.from(metrics, calculator); // then Assertions.assertThat(aggregation.getProductId()).isEqualTo(1L); @@ -38,52 +41,12 @@ void should_create_from_valid_aggregation_result() { } @Test - @DisplayName("null 배열에 대해 예외가 발생한다") - void should_throw_exception_when_row_is_null() { + @DisplayName("null 메트릭에 대해 예외가 발생한다") + void should_throw_exception_when_metrics_is_null() { // given & when & then Assertions.assertThatThrownBy(() -> RankingAggregation.from(null, calculator)) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("집계 결과 배열이 null이거나 길이가 부족합니다"); - } - - @Test - @DisplayName("길이가 부족한 배열에 대해 예외가 발생한다") - void should_throw_exception_when_row_length_is_insufficient() { - // given - Object[] shortRow = {1L, 100L, 50L}; // 길이 3 (6 미만) - - // when & then - Assertions.assertThatThrownBy(() -> RankingAggregation.from(shortRow, calculator)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("집계 결과 배열이 null이거나 길이가 부족합니다"); - } - - @Test - @DisplayName("잘못된 데이터 타입에 대해 예외가 발생한다") - void should_throw_exception_when_data_type_is_invalid() { - // given - Object[] invalidRow = {"invalid", 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000)}; // productId가 String - - // when & then - Assertions.assertThatThrownBy(() -> RankingAggregation.from(invalidRow, calculator)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("집계 결과 데이터 형식이 올바르지 않습니다"); - } - - @Test - @DisplayName("Number 타입의 다양한 형태를 처리한다") - void should_handle_various_number_types() { - // given - Integer, Long, BigDecimal 등 다양한 Number 타입 - Object[] row = {1L, 100, 50L, 10, 5L, java.math.BigDecimal.valueOf(1000)}; - - // when - RankingAggregation aggregation = RankingAggregation.from(row, calculator); - - // then - Assertions.assertThat(aggregation.getViewCount()).isEqualTo(100L); - Assertions.assertThat(aggregation.getLikeCount()).isEqualTo(50L); - Assertions.assertThat(aggregation.getSalesCount()).isEqualTo(10L); - Assertions.assertThat(aggregation.getOrderCount()).isEqualTo(5L); + .hasMessageContaining("집계 결과(metrics)가 null입니다."); } } @@ -95,8 +58,10 @@ class 순위_부여 { @DisplayName("유효한 순위를 부여한다") void should_assign_valid_rank() { // given - Object[] row = {1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000)}; - RankingAggregation aggregation = RankingAggregation.from(row, calculator); + ProductMetricsAggregation metrics = new ProductMetricsAggregation( + 1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000) + ); + RankingAggregation aggregation = RankingAggregation.from(metrics, calculator); // when aggregation.assignRank(1); @@ -109,8 +74,10 @@ void should_assign_valid_rank() { @DisplayName("100위까지 순위를 부여할 수 있다") void should_assign_rank_up_to_100() { // given - Object[] row = {1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000)}; - RankingAggregation aggregation = RankingAggregation.from(row, calculator); + ProductMetricsAggregation metrics = new ProductMetricsAggregation( + 1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000) + ); + RankingAggregation aggregation = RankingAggregation.from(metrics, calculator); // when aggregation.assignRank(100); @@ -123,8 +90,10 @@ void should_assign_rank_up_to_100() { @DisplayName("0 이하의 순위에 대해 예외가 발생한다") void should_throw_exception_when_rank_is_zero_or_negative() { // given - Object[] row = {1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000)}; - RankingAggregation aggregation = RankingAggregation.from(row, calculator); + ProductMetricsAggregation metrics = new ProductMetricsAggregation( + 1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000) + ); + RankingAggregation aggregation = RankingAggregation.from(metrics, calculator); // when & then Assertions.assertThatThrownBy(() -> aggregation.assignRank(0)) @@ -140,8 +109,10 @@ void should_throw_exception_when_rank_is_zero_or_negative() { @DisplayName("100을 초과하는 순위에 대해 예외가 발생한다") void should_throw_exception_when_rank_exceeds_100() { // given - Object[] row = {1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000)}; - RankingAggregation aggregation = RankingAggregation.from(row, calculator); + ProductMetricsAggregation metrics = new ProductMetricsAggregation( + 1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000) + ); + RankingAggregation aggregation = RankingAggregation.from(metrics, calculator); // when & then Assertions.assertThatThrownBy(() -> aggregation.assignRank(101)) @@ -158,8 +129,10 @@ class 문자열_표현 { @DisplayName("toString이 올바른 형식을 반환한다") void should_return_correct_string_format() { // given - Object[] row = {1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000)}; - RankingAggregation aggregation = RankingAggregation.from(row, calculator); + ProductMetricsAggregation metrics = new ProductMetricsAggregation( + 1L, 100L, 50L, 10L, 5L, java.math.BigDecimal.valueOf(1000) + ); + RankingAggregation aggregation = RankingAggregation.from(metrics, calculator); aggregation.assignRank(1); // when diff --git a/apps/commerce-batch/src/test/java/com/loopers/batch/job/ranking/support/RankingAggregatorUnitTest.java b/apps/commerce-batch/src/test/java/com/loopers/batch/job/ranking/support/RankingAggregatorUnitTest.java index 818b6970a..b817b2726 100644 --- a/apps/commerce-batch/src/test/java/com/loopers/batch/job/ranking/support/RankingAggregatorUnitTest.java +++ b/apps/commerce-batch/src/test/java/com/loopers/batch/job/ranking/support/RankingAggregatorUnitTest.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; +import com.loopers.domain.metrics.ProductMetricsAggregation; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -25,10 +26,10 @@ class 랭킹_처리 { @DisplayName("집계 결과를 점수 기준으로 정렬하고 순위를 부여한다") void should_sort_by_score_and_assign_ranks() { // given - List results = List.of( - new Object[]{1L, 100L, 10L, 5L, 2L , new BigDecimal(0)}, - new Object[]{2L, 200L, 20L, 10L, 4L, new BigDecimal(0)}, - new Object[]{3L, 50L, 5L, 2L, 1L, new BigDecimal(0)} + List results = List.of( + new ProductMetricsAggregation(1L, 100L, 10L, 5L, 2L , new BigDecimal(0)), + new ProductMetricsAggregation(2L, 200L, 20L, 10L, 4L, new BigDecimal(0)), + new ProductMetricsAggregation(3L, 50L, 5L, 2L, 1L, new BigDecimal(0)) ); // when @@ -40,25 +41,25 @@ void should_sort_by_score_and_assign_ranks() { // 점수 기준 내림차순 정렬 확인 Assertions.assertThat(rankings.get(0).getProductId()).isEqualTo(2L); // 1위 Assertions.assertThat(rankings.get(0).getRankPosition()).isEqualTo(1); - Assertions.assertThat(rankings.get(0).getTotalScore()).isEqualTo(240L); - + Assertions.assertThat(rankings.get(0).getTotalScore()).isEqualTo(240L); // (200*0.1 + 20*0.2 + log(1)*0.6) * 10 = (20 + 4 + 0) * 10 = 240 + Assertions.assertThat(rankings.get(1).getProductId()).isEqualTo(1L); // 2위 Assertions.assertThat(rankings.get(1).getRankPosition()).isEqualTo(2); - Assertions.assertThat(rankings.get(1).getTotalScore()).isEqualTo(120L); + Assertions.assertThat(rankings.get(1).getTotalScore()).isEqualTo(120L); // (100*0.1 + 10*0.2) * 10 = 120 Assertions.assertThat(rankings.get(2).getProductId()).isEqualTo(3L); // 3위 Assertions.assertThat(rankings.get(2).getRankPosition()).isEqualTo(3); - Assertions.assertThat(rankings.get(2).getTotalScore()).isEqualTo(60L); + Assertions.assertThat(rankings.get(2).getTotalScore()).isEqualTo(60L); // (50*0.1 + 5*0.2) * 10 = 60 } @Test @DisplayName("TOP 100을 초과하는 결과는 필터링된다") void should_filter_results_beyond_top_100() { // given - 150개의 결과 생성 - List results = new ArrayList<>(); + List results = new ArrayList<>(); for (int i = 1; i <= 150; i++) { // 점수가 높은 순서대로 생성 (i가 클수록 점수 높음) - results.add(new Object[]{(long) i, (long) i * 10, (long) i, (long) i, (long) i, new BigDecimal(i)}); + results.add(new ProductMetricsAggregation((long) i, (long) i * 10, (long) i, (long) i, (long) i, new BigDecimal(i))); } // when @@ -74,7 +75,7 @@ void should_filter_results_beyond_top_100() { @DisplayName("빈 결과에 대해 빈 목록을 반환한다") void should_return_empty_list_for_empty_results() { // given - List emptyResults = List.of(); + List emptyResults = List.of(); // when List rankings = aggregator.processRankings(emptyResults); @@ -97,10 +98,10 @@ void should_return_empty_list_for_null_results() { @DisplayName("동일한 점수의 상품들은 순서가 유지된다") void should_maintain_order_for_same_scores() { // given - 동일한 점수를 가진 상품들 - List results = List.of( - new Object[]{1L, 100L, 0L, 0L, 0L, new BigDecimal(0)}, // score = 100 - new Object[]{2L, 100L, 0L, 0L, 0L, new BigDecimal(0)}, // score = 100 - new Object[]{3L, 100L, 0L, 0L, 0L, new BigDecimal(0)} // score = 100 + List results = List.of( + new ProductMetricsAggregation(1L, 100L, 0L, 0L, 0L, new BigDecimal(0)), // score = 100 + new ProductMetricsAggregation(2L, 100L, 0L, 0L, 0L, new BigDecimal(0)), // score = 100 + new ProductMetricsAggregation(3L, 100L, 0L, 0L, 0L, new BigDecimal(0)) // score = 100 ); // when diff --git a/apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java b/apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java index cbdc2bea6..929653aab 100644 --- a/apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java +++ b/apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsJpaRepository.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Optional; +import com.loopers.domain.metrics.ProductMetricsAggregation; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -45,17 +46,18 @@ List findByMetricDateBetween( * 기간별 상품 집계 (GROUP BY) */ @Query(""" - SELECT m.id.productId, + SELECT new com.loopers.domain.metrics.ProductMetricsAggregation( + m.id.productId, SUM(m.viewCount), SUM(m.likeCount), SUM(m.salesCount), SUM(m.orderCount), - SUM(m.totalSalesAmount) + SUM(m.totalSalesAmount)) FROM ProductMetricsEntity m WHERE m.id.metricDate BETWEEN :startDate AND :endDate GROUP BY m.id.productId """) - List aggregateByDateRange( + List aggregateByDateRange( @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); } diff --git a/apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java b/apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java index 015e88095..461aebcb9 100644 --- a/apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java +++ b/apps/commerce-streamer/src/main/java/com/loopers/infrastructure/metrics/ProductMetricsRepositoryImpl.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Component; +import com.loopers.domain.metrics.ProductMetricsAggregation; import com.loopers.domain.metrics.ProductMetricsEntity; import com.loopers.domain.metrics.ProductMetricsId; import com.loopers.domain.metrics.ProductMetricsRepository; @@ -45,7 +46,7 @@ public List findByMetricDateBetween(LocalDate startDate, L } @Override - public List aggregateByDateRange(LocalDate startDate, LocalDate endDate) { + public List aggregateByDateRange(LocalDate startDate, LocalDate endDate) { return productMetricsJpaRepository.aggregateByDateRange(startDate, endDate); } } diff --git a/modules/jpa/src/main/generated/com/loopers/domain/ranking/QWeeklyRankEntity.java b/modules/jpa/src/main/generated/com/loopers/domain/ranking/QWeeklyRankEntity.java index f49bb509a..c0cefa113 100644 --- a/modules/jpa/src/main/generated/com/loopers/domain/ranking/QWeeklyRankEntity.java +++ b/modules/jpa/src/main/generated/com/loopers/domain/ranking/QWeeklyRankEntity.java @@ -30,7 +30,7 @@ public class QWeeklyRankEntity extends EntityPathBase { public final NumberPath orderCount = createNumber("orderCount", Long.class); - public final NumberPath rankPosition = createNumber("rankPosition", Long.class); + public final NumberPath rankPosition = createNumber("rankPosition", Integer.class); public final NumberPath salesCount = createNumber("salesCount", Long.class); diff --git a/modules/jpa/src/main/java/com/loopers/domain/metrics/ProductMetricsEntity.java b/modules/jpa/src/main/java/com/loopers/domain/metrics/ProductMetricsEntity.java index 7b3b27828..c1131478f 100644 --- a/modules/jpa/src/main/java/com/loopers/domain/metrics/ProductMetricsEntity.java +++ b/modules/jpa/src/main/java/com/loopers/domain/metrics/ProductMetricsEntity.java @@ -104,6 +104,7 @@ public void incrementView(ZonedDateTime eventTime) { * @param eventTime 이벤트 발생 시간 */ public void applyLikeDelta(int delta, ZonedDateTime eventTime) { + Objects.requireNonNull(eventTime, "이벤트 시간은 필수입니다."); long next = this.likeCount + delta; this.likeCount = Math.max(0, next); this.lastEventAt = eventTime; diff --git a/modules/jpa/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java b/modules/jpa/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java index ec3c37cdd..b930ba9a5 100644 --- a/modules/jpa/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java +++ b/modules/jpa/src/main/java/com/loopers/domain/metrics/ProductMetricsRepository.java @@ -54,7 +54,7 @@ public interface ProductMetricsRepository { * * @param startDate 시작 날짜 * @param endDate 종료 날짜 - * @return 집계 결과 (productId, viewCount, likeCount, salesCount, orderCount) + * @return 집계 결과 목록 */ - List aggregateByDateRange(LocalDate startDate, LocalDate endDate); + List aggregateByDateRange(LocalDate startDate, LocalDate endDate); } diff --git a/modules/jpa/src/main/java/com/loopers/domain/ranking/MonthlyRankEntity.java b/modules/jpa/src/main/java/com/loopers/domain/ranking/MonthlyRankEntity.java index e4689f76d..d6f25268c 100644 --- a/modules/jpa/src/main/java/com/loopers/domain/ranking/MonthlyRankEntity.java +++ b/modules/jpa/src/main/java/com/loopers/domain/ranking/MonthlyRankEntity.java @@ -46,7 +46,7 @@ public class MonthlyRankEntity { private long totalScore; @Column(name = "base_rank_position", nullable = false) - private long rankPosition; + private int rankPosition; @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; diff --git a/modules/jpa/src/main/java/com/loopers/domain/ranking/WeeklyRankEntity.java b/modules/jpa/src/main/java/com/loopers/domain/ranking/WeeklyRankEntity.java index a54c81390..310bee042 100644 --- a/modules/jpa/src/main/java/com/loopers/domain/ranking/WeeklyRankEntity.java +++ b/modules/jpa/src/main/java/com/loopers/domain/ranking/WeeklyRankEntity.java @@ -20,7 +20,7 @@ @Table( name = "mv_product_rank_weekly", indexes = { - @Index(name = "idx_year_week_rank", columnList = "year_week, rank_position") + @Index(name = "idx_year_week_rank", columnList = "base_year_week, base_rank_position") } ) @Getter @@ -45,8 +45,8 @@ public class WeeklyRankEntity { @Column(name = "total_score", nullable = false) private long totalScore; - @Column(name = "rank_position", nullable = false) - private long rankPosition; + @Column(name = "base_rank_position", nullable = false) + private int rankPosition; @Column(name = "created_at", nullable = false) private LocalDateTime createdAt; diff --git a/modules/jpa/src/main/java/com/loopers/domain/ranking/WeeklyRankId.java b/modules/jpa/src/main/java/com/loopers/domain/ranking/WeeklyRankId.java index 4d16f68b8..7bfc86650 100644 --- a/modules/jpa/src/main/java/com/loopers/domain/ranking/WeeklyRankId.java +++ b/modules/jpa/src/main/java/com/loopers/domain/ranking/WeeklyRankId.java @@ -22,7 +22,7 @@ public class WeeklyRankId implements Serializable { @Column(name = "product_id", nullable = false) private Long productId; - @Column(name = "year_week", nullable = false, length = 8) + @Column(name = "base_year_week", nullable = false, length = 8) private String yearWeek; // e.g., "2024-W52" private WeeklyRankId(Long productId, String yearWeek) {