From fbcf9147223181a9169b2cd7cfa687e02cb7bec8 Mon Sep 17 00:00:00 2001 From: non Date: Thu, 16 May 2024 16:35:45 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=EC=88=98=EB=9F=89=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../market/domain/product/entity/Product.java | 9 +++- .../product/repository/ProductRepository.java | 13 +++-- .../product/service/ProductService.java | 7 ++- .../product/service/ProductServiceTest.java | 50 +++++++++++++------ 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/blanc/market/domain/product/entity/Product.java b/src/main/java/com/blanc/market/domain/product/entity/Product.java index 0c8e894..2d58aa4 100644 --- a/src/main/java/com/blanc/market/domain/product/entity/Product.java +++ b/src/main/java/com/blanc/market/domain/product/entity/Product.java @@ -5,10 +5,9 @@ import com.blanc.market.domain.review.entity.Review; import com.blanc.market.domain.searchHistory.entity.SearchHistory; import com.blanc.market.global.entity.BaseEntity; +import org.hibernate.annotations.Where; import jakarta.persistence.*; import lombok.*; -import org.hibernate.annotations.LazyGroup; -import org.hibernate.annotations.Where; import java.util.HashSet; import java.util.List; @@ -45,6 +44,8 @@ public class Product extends BaseEntity { @OneToMany(mappedBy = "product", fetch = FetchType.LAZY) private List searchHistories; + private int count; + private int likeCount; @@ -78,6 +79,10 @@ public void addProductIngredient(ProductIngredient productIngredient) { productIngredients.add(productIngredient); } + public void setCount(int count) { + this.count = count; + } + public void setLikeCount(int likeCount) { this.likeCount = likeCount; } diff --git a/src/main/java/com/blanc/market/domain/product/repository/ProductRepository.java b/src/main/java/com/blanc/market/domain/product/repository/ProductRepository.java index a3ca15b..2e2e87d 100644 --- a/src/main/java/com/blanc/market/domain/product/repository/ProductRepository.java +++ b/src/main/java/com/blanc/market/domain/product/repository/ProductRepository.java @@ -2,13 +2,16 @@ import com.blanc.market.domain.product.entity.Category; import com.blanc.market.domain.product.entity.Product; +import jakarta.persistence.LockModeType; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Lock; import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; @Repository public interface ProductRepository extends JpaRepository { @@ -16,12 +19,14 @@ public interface ProductRepository extends JpaRepository { List findByNameContaining(String keyword); Page findByNameContaining(String keyword, Pageable pageable); Page findByCategory(Category category, Pageable pageable); - - @Query(value = "select get_lock(:key, 1000)", nativeQuery = true) + + @Lock(LockModeType.PESSIMISTIC_WRITE) + @Query("select p from Product p where p.id = :id") + Optional findByIdWithPessimisticLock(Long id); + + @Query(value = "select get_lock(:key, 128)", nativeQuery = true) void getLock(String key); @Query(value = "select release_lock(:key)", nativeQuery = true) void releaseLock(String key); - - } diff --git a/src/main/java/com/blanc/market/domain/product/service/ProductService.java b/src/main/java/com/blanc/market/domain/product/service/ProductService.java index 885487f..3a09a41 100644 --- a/src/main/java/com/blanc/market/domain/product/service/ProductService.java +++ b/src/main/java/com/blanc/market/domain/product/service/ProductService.java @@ -13,7 +13,6 @@ import com.blanc.market.domain.product.dto.ProductRequest; import com.blanc.market.domain.product.dto.ProductResponse; import com.blanc.market.domain.product.dto.ProductUpdateRequest; -import com.blanc.market.domain.product.entity.Product; import com.blanc.market.domain.product.mapper.ProductMapper; import com.blanc.market.domain.product.repository.ProductRepository; import com.blanc.market.domain.review.dto.ReviewResponse; @@ -157,6 +156,12 @@ public void updateProduct(Long productId, ProductUpdateRequest request) { product.update(request); } + @Transactional + public void updateProductCount(Long productId, int incrementValue) { + Product product = productRepository.findByIdWithPessimisticLock(productId).orElseThrow(); + product.setCount(product.getCount() + incrementValue); + } + @Transactional(propagation = Propagation.REQUIRES_NEW) public void updateLikeCount(Long productId, int incrementValue) { Product product = productRepository.findById(productId).orElseThrow(); diff --git a/src/test/java/com/blanc/market/domain/product/service/ProductServiceTest.java b/src/test/java/com/blanc/market/domain/product/service/ProductServiceTest.java index 7886305..ebb47b4 100644 --- a/src/test/java/com/blanc/market/domain/product/service/ProductServiceTest.java +++ b/src/test/java/com/blanc/market/domain/product/service/ProductServiceTest.java @@ -36,6 +36,25 @@ class ProductServiceTest { private Product testProduct; + private final int taskCount = 32; + + private void executeParallel(int taskCount, Runnable runnable) throws InterruptedException { + ExecutorService executorService = Executors.newFixedThreadPool(8); + CountDownLatch latch = new CountDownLatch(taskCount); + + for (int i = 0; i < taskCount; i++) { + executorService.submit(() -> { + try { + runnable.run(); + } finally { + latch.countDown(); + } + }); + } + + latch.await(); + } + @BeforeEach void setUp() { testProduct = productRepository.save(Product.builder() @@ -103,27 +122,26 @@ void getReviewsForProduct() { @Test @Transactional(propagation = Propagation.NEVER) - void updateLikeCount() throws InterruptedException { - int initialLikeCount = testProduct.getLikeCount(); + void updateCount() throws InterruptedException { + int initialCount = testProduct.getCount(); - int threadCount = 32; - ExecutorService executorService = Executors.newFixedThreadPool(8); - CountDownLatch latch = new CountDownLatch(threadCount); + this.executeParallel(this.taskCount, + () -> productService.updateProductCount(testProduct.getId(), 1)); - for (int i = 0; i < threadCount; i++) { - executorService.submit(() -> { - try { - namedLockProductFacade.updateLikeCount(testProduct.getId(), 1); - } finally { - latch.countDown(); - } - }); - } + Product product = productRepository.findById(testProduct.getId()).orElseThrow(); + assertEquals(initialCount + this.taskCount, product.getCount()); + } - latch.await(); + @Test + @Transactional(propagation = Propagation.NEVER) + void updateLikeCount() throws InterruptedException { + int initialLikeCount = testProduct.getLikeCount(); + + this.executeParallel(this.taskCount, + () -> namedLockProductFacade.updateLikeCount(testProduct.getId(), 1)); Product product = productRepository.findById(testProduct.getId()).orElseThrow(); - assertEquals(initialLikeCount + threadCount, product.getLikeCount()); + assertEquals(initialLikeCount + this.taskCount, product.getLikeCount()); } @Test