diff --git a/backend/src/main/java/com/woowacourse/f12/application/batch/BatchService.java b/backend/src/main/java/com/woowacourse/f12/application/batch/BatchService.java new file mode 100644 index 00000000..309f53cb --- /dev/null +++ b/backend/src/main/java/com/woowacourse/f12/application/batch/BatchService.java @@ -0,0 +1,20 @@ +package com.woowacourse.f12.application.batch; + +import com.woowacourse.f12.domain.member.MemberRepository; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class BatchService { + + private final MemberRepository memberRepository; + + public BatchService(final MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Transactional + public void updateFollowerCount() { + memberRepository.updateFollowerCountBatch(); + } +} diff --git a/backend/src/main/java/com/woowacourse/f12/application/batch/FollowerCountBatchScheduler.java b/backend/src/main/java/com/woowacourse/f12/application/batch/FollowerCountBatchScheduler.java new file mode 100644 index 00000000..0caaed3a --- /dev/null +++ b/backend/src/main/java/com/woowacourse/f12/application/batch/FollowerCountBatchScheduler.java @@ -0,0 +1,25 @@ +package com.woowacourse.f12.application.batch; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; + +@Slf4j +public class FollowerCountBatchScheduler { + + private static final String LOG_FORMAT = "Class : {}, Message : {}"; + + private final BatchService batchService; + + public FollowerCountBatchScheduler(final BatchService batchService) { + this.batchService = batchService; + } + + @Scheduled(cron = "0 0 0/1 1/1 * ? *") + public void execute() { + try { + batchService.updateFollowerCount(); + } catch (Exception e) { + log.error(LOG_FORMAT, e.getClass().getSimpleName(), e.getMessage()); + } + } +} diff --git a/backend/src/main/java/com/woowacourse/f12/application/review/ReviewService.java b/backend/src/main/java/com/woowacourse/f12/application/review/ReviewService.java index ed3fbff7..a3307577 100644 --- a/backend/src/main/java/com/woowacourse/f12/application/review/ReviewService.java +++ b/backend/src/main/java/com/woowacourse/f12/application/review/ReviewService.java @@ -113,7 +113,6 @@ public void update(final Long reviewId, final Long memberId, final ReviewRequest final Review updateReview = updateRequest.toReview(target.getProduct(), target.getMember()); int ratingGap = updateReview.getRating() - target.getRating(); target.update(updateReview); - reviewRepository.flush(); productRepository.updateProductStatisticsForReviewUpdate(target.getProduct().getId(), ratingGap); } @@ -125,8 +124,6 @@ public void delete(final Long reviewId, final Long memberId) { .orElseThrow(InventoryProductNotFoundException::new); inventoryProductRepository.delete(inventoryProduct); reviewRepository.delete(review); -// inventoryProductRepository.flush(); - reviewRepository.flush(); productRepository.updateProductStatisticsForReviewDelete(review.getProduct().getId(), review.getRating()); } diff --git a/backend/src/main/java/com/woowacourse/f12/config/SchedulerConfig.java b/backend/src/main/java/com/woowacourse/f12/config/SchedulerConfig.java new file mode 100644 index 00000000..a8b9a0ff --- /dev/null +++ b/backend/src/main/java/com/woowacourse/f12/config/SchedulerConfig.java @@ -0,0 +1,23 @@ +package com.woowacourse.f12.config; + +import com.woowacourse.f12.application.batch.BatchService; +import com.woowacourse.f12.application.batch.FollowerCountBatchScheduler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +@EnableScheduling +@Configuration +public class SchedulerConfig { + + private final BatchService batchService; + + public SchedulerConfig(final BatchService batchService) { + this.batchService = batchService; + } + + @Bean(name = "followerCountBatchScheduler") + public FollowerCountBatchScheduler followerCountBatchScheduler() { + return new FollowerCountBatchScheduler(batchService); + } +} diff --git a/backend/src/main/java/com/woowacourse/f12/domain/member/MemberRepository.java b/backend/src/main/java/com/woowacourse/f12/domain/member/MemberRepository.java index 1bc2584d..0f3beb74 100644 --- a/backend/src/main/java/com/woowacourse/f12/domain/member/MemberRepository.java +++ b/backend/src/main/java/com/woowacourse/f12/domain/member/MemberRepository.java @@ -2,8 +2,14 @@ import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; public interface MemberRepository extends JpaRepository, MemberRepositoryCustom { Optional findByGitHubId(String gitHubId); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query(value = "update Member m set m.followerCount = (select count(f) from Following f where f.followingId = m.id)") + void updateFollowerCountBatch(); } diff --git a/backend/src/main/java/com/woowacourse/f12/domain/product/ProductRepository.java b/backend/src/main/java/com/woowacourse/f12/domain/product/ProductRepository.java index 7d275596..9217606d 100644 --- a/backend/src/main/java/com/woowacourse/f12/domain/product/ProductRepository.java +++ b/backend/src/main/java/com/woowacourse/f12/domain/product/ProductRepository.java @@ -9,7 +9,7 @@ public interface ProductRepository extends JpaRepository, Product List findByReviewCountGreaterThanEqualAndRatingGreaterThanEqual(int reviewCount, double rating); - @Modifying(clearAutomatically = true) + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query(value = "update Product p " + "set p.rating = (p.totalRating + :reviewRating) / cast((p.reviewCount + 1) as double), " + "p.reviewCount = p.reviewCount + 1, " @@ -17,7 +17,7 @@ public interface ProductRepository extends JpaRepository, Product + "where p.id = :productId") void updateProductStatisticsForReviewInsert(Long productId, int reviewRating); - @Modifying(clearAutomatically = true) + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query(value = "update Product p " + "set p.rating = case p.reviewCount when 1 then 0 " + "else ((p.totalRating - :reviewRating) / cast((p.reviewCount - 1) as double)) end , " @@ -26,7 +26,7 @@ public interface ProductRepository extends JpaRepository, Product + "where p.id = :productId") void updateProductStatisticsForReviewDelete(Long productId, int reviewRating); - @Modifying(clearAutomatically = true) + @Modifying(clearAutomatically = true, flushAutomatically = true) @Query(value = "update Product p " + "set p.rating = (p.totalRating + :ratingGap) / cast(p.reviewCount as double), " + "p.totalRating = p.totalRating + :ratingGap " diff --git a/backend/src/test/java/com/woowacourse/f12/domain/member/MemberRepositoryTest.java b/backend/src/test/java/com/woowacourse/f12/domain/member/MemberRepositoryTest.java index bc07a1fa..5716c633 100644 --- a/backend/src/test/java/com/woowacourse/f12/domain/member/MemberRepositoryTest.java +++ b/backend/src/test/java/com/woowacourse/f12/domain/member/MemberRepositoryTest.java @@ -315,4 +315,27 @@ class MemberRepositoryTest { assertThatThrownBy(() -> memberRepository.save(member2)) .isInstanceOf(DataIntegrityViolationException.class); } + + @Test + void 회원의_팔로워_수의_정합성을_맞춘다() { + // given + Member member = CORINNE.생성(); + Member follower = MINCHO.생성(); + memberRepository.save(member); + memberRepository.save(follower); + Following following = Following.builder() + .followerId(follower.getId()) + .followingId(member.getId()) + .build(); + followingRepository.save(following); + + // when + memberRepository.updateFollowerCountBatch(); + + // then + Member actual = memberRepository.findById(member.getId()) + .orElseThrow(); + + assertThat(actual.getFollowerCount()).isOne(); + } }