Skip to content

Commit cd86eaf

Browse files
committed
fix: 코드 리뷰 반영
- 사용하지 않는 변수 삭제 - review_rating 컬럼에 NOT NULL 제약 추가 - 토글 기능 동시성 문제 발생 가능으로 비관적 락을 걸어 문제 해결
1 parent 489c25c commit cd86eaf

File tree

4 files changed

+17
-10
lines changed

4 files changed

+17
-10
lines changed

src/main/java/com/ongil/backend/domain/product/entity/Product.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public class Product extends BaseEntity {
7070
@Column(name = "review_count")
7171
private Integer reviewCount = 0;
7272

73-
@Column(name = "review_rating")
73+
@Column(name = "review_rating", nullable = false)
7474
private Double reviewRating = 0.0;
7575

7676
@Formula("coalesce(view_count, 0) + coalesce(purchase_count, 0)")

src/main/java/com/ongil/backend/domain/review/repository/ReviewRepository.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
package com.ongil.backend.domain.review.repository;
22

33
import java.util.List;
4+
import java.util.Optional;
45

56
import org.springframework.data.domain.Page;
67
import org.springframework.data.domain.Pageable;
78
import org.springframework.data.jpa.repository.EntityGraph;
89
import org.springframework.data.jpa.repository.JpaRepository;
10+
import org.springframework.data.jpa.repository.Lock;
911
import org.springframework.data.jpa.repository.Query;
1012
import org.springframework.data.repository.query.Param;
1113

1214
import com.ongil.backend.domain.review.entity.Review;
1315
import com.ongil.backend.domain.review.enums.ReviewStatus;
1416
import com.ongil.backend.domain.review.enums.ReviewType;
1517

18+
import jakarta.persistence.LockModeType;
19+
1620
public interface ReviewRepository extends JpaRepository<Review, Long> {
1721

1822
// 상품별 리뷰 목록 조회 (필터 없음)
@@ -142,7 +146,7 @@ List<Object[]> countByMaterialAnswer(
142146
);
143147

144148
// 상품별 평균 별점
145-
@Query("SELECT AVG(r.rating) FROM Review r " +
149+
@Query("SELECT COALESCE(AVG(r.rating), 0.0) FROM Review r " +
146150
"WHERE r.product.id = :productId " +
147151
"AND r.reviewStatus = 'COMPLETED'")
148152
Double getAverageRating(@Param("productId") Long productId);
@@ -173,4 +177,9 @@ Page<Review> findByUserIdAndReviewStatusAndReviewType(
173177
// OrderItem에 대해 이미 작성된 리뷰 타입 확인
174178
boolean existsByOrderItemIdAndReviewType(Long orderItemId, ReviewType reviewType);
175179

180+
// ReviewRepository.java
181+
182+
@Lock(LockModeType.PESSIMISTIC_WRITE)
183+
@Query("SELECT r FROM Review r WHERE r.id = :reviewId")
184+
Optional<Review> findByIdWithLock(@Param("reviewId") Long reviewId);
176185
}

src/main/java/com/ongil/backend/domain/review/service/ReviewService.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ public Page<MyReviewResponse> getMyReviews(Long userId, ReviewType reviewType, i
147147

148148
// 5. 작성 가능한 리뷰 목록 조회
149149
public List<PendingReviewResponse> getPendingReviews(Long userId) {
150-
User user = findUserById(userId);
151150
List<PendingReviewResponse> pendingReviews = new ArrayList<>();
152151

153152
// 사용자의 주문 상품 중 리뷰 작성 가능한 항목 조회
@@ -175,19 +174,18 @@ public List<PendingReviewResponse> getPendingReviews(Long userId) {
175174
// 6. 리뷰 도움돼요 토글
176175
@Transactional
177176
public ReviewHelpfulResponse toggleHelpful(Long reviewId, Long userId) {
178-
Review review = reviewRepository.findById(reviewId)
177+
// 리뷰에 대한 PESSIMISTIC WRITE 락 획득
178+
Review review = reviewRepository.findByIdWithLock(reviewId)
179179
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.REVIEW_NOT_FOUND));
180180

181181
User user = findUserById(userId);
182182

183183
boolean exists = reviewHelpfulRepository.existsByReviewIdAndUserId(reviewId, userId);
184184

185185
if (exists) {
186-
// 이미 눌렀으면 취소(악의적으로 중복으로 누르는 경우 방지)
187186
reviewHelpfulRepository.deleteByReviewIdAndUserId(reviewId, userId);
188187
review.decrementHelpfulCount();
189188
} else {
190-
// 안 눌렀으면 추가
191189
ReviewHelpful helpful = ReviewHelpful.builder()
192190
.review(review)
193191
.user(user)

src/main/java/com/ongil/backend/global/config/SecurityConfig.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.springframework.context.annotation.Bean;
44
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.http.HttpMethod;
56
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
67
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
78
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -55,10 +56,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
5556
.requestMatchers("/api/categories/**").permitAll()
5657
.requestMatchers("/api/search/**").permitAll()
5758

58-
.requestMatchers("/api/reviews/*/details").permitAll() // 리뷰 상세 조회 (비로그인 가능)
59-
.requestMatchers("/api/reviews/*/helpful").authenticated() // 도움돼요 토글 (로그인 필수)
60-
.requestMatchers("/api/users/me/reviews/**").authenticated() // 내 리뷰 관련 (로그인 필수)
61-
59+
.requestMatchers(HttpMethod.GET, "/api/reviews/*/details").permitAll()
60+
.requestMatchers(HttpMethod.POST, "/api/reviews/*/helpful").authenticated()
61+
.requestMatchers(HttpMethod.GET, "/api/users/me/reviews/**").authenticated()
6262
// [6] 그 외 모든 요청은 로그인(인증) 필요
6363
.anyRequest().authenticated()
6464
)

0 commit comments

Comments
 (0)