Skip to content

Commit

Permalink
[Feature/be] 동시성으로 인한 데이터 정합성 문제 해결 (#790)
Browse files Browse the repository at this point in the history
* [BE] 리뷰 작성 시 제품 통계 정보의 정합성을 맞추도록 수정 (#784)

* feat: 제품 통계 정합성 맞추는 쿼리 작성

* refactor: 서비스에서 제품 통계 업데이트를 쿼리를 직접 사용하도록 수정

* [BE] 데이터 정합성을 맞추는 스케줄러 작성 (#789)

* feat: 회원 팔로워 수의 데이터 정합성을 배치 처리하는 쿼리 작성

* refactor: 서비스 레이어에서 명시적 flush 대신 벌크 쿼리의 flushAutomatically 모드를 true로 설정

* feat: 1시간마다 회원의 팔로워 수 정합성을 맞추는 배치 쿼리를 실행하는 스케줄러 구현

* fix: 스케줄러가 테스트 환경에서는 빈으로 등록되지 않도록 수정

* [BE] 회원 정합성 문제 해결 배치 스케줄 대신 실시간으로 정합성을 맞추는 쿼리로 교체 (#809)

* feat: 팔로워 수를 증가 / 감소 시키는 쿼리 작성

* refactor: 더티 체킹 대신 팔로워 수 조작 쿼리를 사용하도록 수정

* fix: 불필요한 쿼리 및 객체 제거

* refactor: 코드리뷰 반영
  • Loading branch information
Ohzzi authored Oct 18, 2022
1 parent e88ee94 commit 5b35da2
Show file tree
Hide file tree
Showing 16 changed files with 237 additions and 316 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -139,20 +139,19 @@ private List<Long> extractMemberIds(final List<Member> members) {

@Transactional
public void follow(final Long followerId, final Long followingId) {
final Member targetMember = memberRepository.findById(followingId)
.orElseThrow(MemberNotFoundException::new);
validateFollowingMembersExist(followerId);
validateMemberExists(followerId);
validateMemberExists(followingId);
validateNotFollowing(followerId, followingId);
final Following following = Following.builder()
.followerId(followerId)
.followingId(followingId)
.build();
followingRepository.save(following);
targetMember.increaseFollowerCount();
memberRepository.increaseFollowerCount(followingId);
}

private void validateFollowingMembersExist(final Long followerId) {
if (!memberRepository.existsById(followerId)) {
private void validateMemberExists(final Long memberId) {
if (!memberRepository.existsById(memberId)) {
throw new MemberNotFoundException();
}
}
Expand All @@ -165,12 +164,11 @@ private void validateNotFollowing(final Long followerId, final Long followingId)

@Transactional
public void unfollow(final Long followerId, final Long followingId) {
final Member targetMember = memberRepository.findById(followingId)
.orElseThrow(MemberNotFoundException::new);
validateFollowingMembersExist(followerId);
validateMemberExists(followerId);
validateMemberExists(followingId);
final Following following = findFollowingRelation(followerId, followingId);
followingRepository.delete(following);
targetMember.decreaseFollowerCount();
memberRepository.decreaseFollowerCount(followingId);
}

private Following findFollowingRelation(final Long followerId, final Long followingId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public Long saveReviewAndInventoryProduct(final Long productId, final Long membe
.orElseThrow(ProductNotFoundException::new);
final Long reviewId = saveReview(reviewRequest, member, product);
saveInventoryProduct(member, product);
productRepository.updateProductStatisticsForReviewInsert(product.getId(), reviewRequest.getRating());
return reviewId;
}

Expand All @@ -64,7 +65,6 @@ private void validateRegisterCompleted(final Member member) {
private Long saveReview(final ReviewRequest reviewRequest, final Member member, final Product product) {
validateNotWritten(member, product);
final Review review = reviewRequest.toReview(product, member);
review.reflectToProductWhenWritten();
return reviewRepository.save(review)
.getId();
}
Expand Down Expand Up @@ -111,18 +111,20 @@ public ReviewWithAuthorAndProductPageResponse findPage(final Pageable pageable)
public void update(final Long reviewId, final Long memberId, final ReviewRequest updateRequest) {
final Review target = findTarget(reviewId, memberId);
final Review updateReview = updateRequest.toReview(target.getProduct(), target.getMember());
int ratingGap = updateReview.getRating() - target.getRating();
target.update(updateReview);
productRepository.updateProductStatisticsForReviewUpdate(target.getProduct().getId(), ratingGap);
}

@Transactional
public void delete(final Long reviewId, final Long memberId) {
final Review review = findTarget(reviewId, memberId);
review.reflectToProductBeforeDelete();
reviewRepository.delete(review);
final InventoryProduct inventoryProduct = inventoryProductRepository.findWithProductByMemberAndProduct(
review.getMember(), review.getProduct())
.orElseThrow(InventoryProductNotFoundException::new);
inventoryProductRepository.delete(inventoryProduct);
reviewRepository.delete(review);
productRepository.updateProductStatisticsForReviewDelete(review.getProduct().getId(), review.getRating());
}

private Review findTarget(final Long reviewId, final Long memberId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.woowacourse.f12.domain.inventoryproduct.InventoryProduct;
import com.woowacourse.f12.domain.inventoryproduct.InventoryProducts;
import com.woowacourse.f12.exception.badrequest.InvalidFollowerCountException;
import java.util.List;
import java.util.Objects;
import javax.persistence.Column;
Expand Down Expand Up @@ -114,17 +113,6 @@ private void updateJobType(final JobType jobType) {
}
}

public void increaseFollowerCount() {
this.followerCount += 1;
}

public void decreaseFollowerCount() {
if (this.followerCount == 0) {
throw new InvalidFollowerCountException();
}
this.followerCount -= 1;
}

private void updateRegistered(final boolean registered) {
if (this.registered) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@

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<Member, Long>, MemberRepositoryCustom {

Optional<Member> findByGitHubId(String gitHubId);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query(value = "update Member m set m.followerCount = m.followerCount + 1 where m.id = :followingMemberId")
void increaseFollowerCount(Long followingMemberId);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query(value = "update Member m set m.followerCount = case m.followerCount when 0 then 0 "
+ "else (m.followerCount - 1) end where m.id = :followingMemberId")
void decreaseFollowerCount(Long followingMemberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,34 @@

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

public interface ProductRepository extends JpaRepository<Product, Long>, ProductRepositoryCustom {

List<Product> findByReviewCountGreaterThanEqualAndRatingGreaterThanEqual(int reviewCount, double rating);

@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, "
+ "p.totalRating = p.totalRating + :reviewRating "
+ "where p.id = :productId")
void updateProductStatisticsForReviewInsert(Long productId, int reviewRating);

@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 , "
+ "p.reviewCount = p.reviewCount - 1, "
+ "p.totalRating = p.totalRating - :reviewRating "
+ "where p.id = :productId")
void updateProductStatisticsForReviewDelete(Long productId, int reviewRating);

@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 "
+ "where p.id = :productId")
void updateProductStatisticsForReviewUpdate(Long productId, int ratingGap);
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,25 +88,13 @@ private void validateRating(final int rating) {
}
}

public void reflectToProductWhenWritten() {
product.increaseReviewCount();
product.increaseRating(rating);
}

public void reflectToProductBeforeDelete() {
product.decreaseReviewCount();
product.decreaseRating(rating);
}

public boolean isWrittenBy(final Member member) {
return this.member.equals(member);
}

public void update(final Review updateReview) {
product.decreaseRating(rating);
content = updateReview.getContent();
rating = updateReview.getRating();
product.increaseRating(rating);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ public enum ErrorCode {
SELF_FOLLOW("40050"),
ALREADY_FOLLOWING("40051"),
NOT_FOLLOWING("40052"),
INVALID_FOLLOWER_COUNT("40053"),

REQUEST_DUPLICATED("40060"),

Expand Down

This file was deleted.

Loading

0 comments on commit 5b35da2

Please sign in to comment.