ewhk9887/korean_code_reviews_from_github huggingface dataset:
@@ -0,0 +1,72 @@
+package com.frame2.server.core.board.api;
+
+import com.frame2.server.core.board.application.ProductReviewService;
+import com.frame2.server.core.board.payload.request.ProductReviewModifyRequest;
+import com.frame2.server.core.board.payload.request.ProductReviewRequest;
+import com.frame2.server.core.board.payload.response.ProductReviewResponse;
+import com.frame2.server.core.support.annotations.Auth;
+import com.frame2.server.core.support.annotations.MemberOnly;
+import com.frame2.server.core.support.request.User;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/products/{saleProductId}/reviews")
+public class ProductReviewApi implements ProductReviewApiSpec {
+
+    private final ProductReviewService productReviewService;
+
+    // create
+    @Override
+    @MemberOnly
+    @PostMapping
+    public void createProductReview(
+            @Valid @RequestBody ProductReviewRequest productReviewRequest,
+            @Auth User user,
+            @PathVariable("saleProductId") Long saleProductId
+    ) {
+        productReviewService.productReviewCreate(productReviewRequest, user.id(), saleProductId);
+    }
+
+    // update
+    @Override
+    @MemberOnly
+    @PatchMapping("/{productReviewId}")
+    public void updateProductReview(
+            @Valid @RequestBody ProductReviewModifyRequest ProductReviewModifyRequest,
+            @Auth User user,
+            @PathVariable("productReviewId") Long productReviewId
+    ) {
+        productReviewService.productReviewModify(ProductReviewModifyRequest, productReviewId);
+    }
+
+    // delete
+    @Override
+    @MemberOnly
+    @DeleteMapping("/{productReviewId}")
+    public void deleteProductReview(@PathVariable("productReviewId") Long id, @Auth User user) {
+        productReviewService.remove(id);
+    }
+
+    // 단건 조회
+    @Override
+    @GetMapping("/{productReviewId}")
+    public ResponseEntity<ProductReviewResponse> getProductReview(@PathVariable("productReviewId") Long productReviewId) {
+        ProductReviewResponse productReviewResponse = productReviewService.getProductReview(productReviewId);
+        return ResponseEntity.ok().body(productReviewResponse);
+    }
+
+    // 한 상품에 대한 전체 리뷰 조회
+    @Override
+    @GetMapping
+    public ResponseEntity<List<ProductReviewResponse>> getProductReviewList(
+            @PathVariable("saleProductId") Long saleProductId) {
+        List<ProductReviewResponse> productReviewList = productReviewService.getProductReviewList(saleProductId);
+        return ResponseEntity.ok().body(productReviewList);
+    }
+}

리뷰: 리뷰 단건 조회와 전체 조회에서만  ReponseEntity.ok()를 사용하셨는데, 
리뷰 등록 성공의 경우 `ResponseEntity.create()`을 사용할 수도 있을 것 같아요! 
ResponseEntity 사용과 Http 상태코드에 대해 간략하게 살펴보시면 좋을 것 같습니다 !!
(수정이 필요하다고 생각하진 않습니다❗)
@@ -0,0 +1,45 @@
+package com.frame2.server.core.board.domain;
+
+import com.frame2.server.core.member.domain.Member;
+import com.frame2.server.core.order.domain.OrderDetail;
+import com.frame2.server.core.product.domain.SaleProduct;
+import com.frame2.server.core.support.entity.BaseEntity;
+import jakarta.persistence.*;
+import lombok.*;
+
+@Entity
+@Getter
+@Builder
+@AllArgsConstructor
+@Table(name = "product_reviews")
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+public class ProductReview extends BaseEntity {
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "sale_product_id", nullable = false)
+    private SaleProduct saleProduct;
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "member_id", nullable = false)
+    private Member member;
+
+    @OneToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "order_detail_id", nullable = false)
+    private OrderDetail orderDetail;
+
+    // 평점
+    @Column(nullable = false)
+    private int rating;
+
+    //리뷰 내용
+    @Column(nullable = false)
+    private String contents;
+
+    private String image;
+
+    public void updateProductReview(ProductReview newProductReview) {
+        this.rating = newProductReview.getRating();
+        this.contents = newProductReview.getContents();
+        this.image = newProductReview.getImage();
+    }
+}

리뷰: `ProductReview` 엔티티는 `BaseEntity`를 상속 받고 있습니다 !
`BaseEntity`를 보시면 `deletStatus` 라는 삭제여부를 관리하는 필드가 있어요.
해당 필드와 상품 리뷰 기능 구현 내에서 리뷰 삭제 로직이 충돌하지는 않는지 고민해보셨으면 좋겠습니다🍀
@@ -0,0 +1,70 @@
+package com.frame2.server.core.board.application;
+
+import com.frame2.server.core.board.domain.ProductReview;
+import com.frame2.server.core.board.infrastructure.ProductReviewRepository;
+import com.frame2.server.core.board.payload.request.ProductReviewModifyRequest;
+import com.frame2.server.core.board.payload.request.ProductReviewRequest;
+import com.frame2.server.core.board.payload.response.ProductReviewResponse;
+import com.frame2.server.core.member.domain.Member;
+import com.frame2.server.core.member.infrastructure.MemberRepository;
+import com.frame2.server.core.order.domain.OrderDetail;
+import com.frame2.server.core.order.infrastructure.OrderDetailRepository;
+import com.frame2.server.core.product.domain.SaleProduct;
+import com.frame2.server.core.product.infrastructure.SaleProductRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+@Transactional
+@RequiredArgsConstructor
+public class ProductReviewServiceImpl implements ProductReviewService {
+
+    private final ProductReviewRepository productReviewRepository;
+    private final SaleProductRepository saleProductRepository;
+    private final MemberRepository memberRepository;
+    private final OrderDetailRepository orderDetailRepository;
+
+    // 생성
+    @Override
+    public void productReviewCreate(ProductReviewRequest request, Long memberId, Long saleProductId) {
+        Member member = memberRepository.findOne(memberId);
+        SaleProduct saleProduct = saleProductRepository.findOne(saleProductId);
+        OrderDetail orderDetail = orderDetailRepository.findOne(request.orderDetailId());
+
+        ProductReview productReview = request.toEntity(member, saleProduct, orderDetail);
+        productReviewRepository.save(productReview);
+    }
+
+    // 수정
+    @Override
+    public void productReviewModify(ProductReviewModifyRequest request, Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).updateProductReview(request.toEntity());
+    }
+
+    // 삭제
+    @Override
+    public void remove(Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).delete();
+    }
+
+    // 단건 조회
+    @Override
+    @Transactional(readOnly = true)
+    public ProductReviewResponse getProductReview(Long productReviewId) {
+        ProductReview productReview = productReviewRepository.findOne(productReviewId);
+        return ProductReviewResponse.from(productReview);
+    }
+
+    // 한 상품에 대한 전체 리뷰 조회
+    @Override
+    @Transactional(readOnly = true)
+    public List<ProductReviewResponse> getProductReviewList(Long saleProductId) {
+        List<ProductReview> productReviewList = productReviewRepository.findAllBySaleProductId(saleProductId);
+        return productReviewList.stream()
+                .map(ProductReviewResponse::from)
+                .toList();
+    }
+}

리뷰: 만약 해당 `id`를 가진 리뷰가 없다면 어떡하죠  ..?!
리뷰가 있는지 없는지 먼저 확인해보는 작업이 필요하다고 생각합니다!
@@ -0,0 +1,70 @@
+package com.frame2.server.core.board.application;
+
+import com.frame2.server.core.board.domain.ProductReview;
+import com.frame2.server.core.board.infrastructure.ProductReviewRepository;
+import com.frame2.server.core.board.payload.request.ProductReviewModifyRequest;
+import com.frame2.server.core.board.payload.request.ProductReviewRequest;
+import com.frame2.server.core.board.payload.response.ProductReviewResponse;
+import com.frame2.server.core.member.domain.Member;
+import com.frame2.server.core.member.infrastructure.MemberRepository;
+import com.frame2.server.core.order.domain.OrderDetail;
+import com.frame2.server.core.order.infrastructure.OrderDetailRepository;
+import com.frame2.server.core.product.domain.SaleProduct;
+import com.frame2.server.core.product.infrastructure.SaleProductRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+@Transactional
+@RequiredArgsConstructor
+public class ProductReviewServiceImpl implements ProductReviewService {
+
+    private final ProductReviewRepository productReviewRepository;
+    private final SaleProductRepository saleProductRepository;
+    private final MemberRepository memberRepository;
+    private final OrderDetailRepository orderDetailRepository;
+
+    // 생성
+    @Override
+    public void productReviewCreate(ProductReviewRequest request, Long memberId, Long saleProductId) {
+        Member member = memberRepository.findOne(memberId);
+        SaleProduct saleProduct = saleProductRepository.findOne(saleProductId);
+        OrderDetail orderDetail = orderDetailRepository.findOne(request.orderDetailId());
+
+        ProductReview productReview = request.toEntity(member, saleProduct, orderDetail);
+        productReviewRepository.save(productReview);
+    }
+
+    // 수정
+    @Override
+    public void productReviewModify(ProductReviewModifyRequest request, Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).updateProductReview(request.toEntity());
+    }
+
+    // 삭제
+    @Override
+    public void remove(Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).delete();
+    }
+
+    // 단건 조회
+    @Override
+    @Transactional(readOnly = true)
+    public ProductReviewResponse getProductReview(Long productReviewId) {
+        ProductReview productReview = productReviewRepository.findOne(productReviewId);
+        return ProductReviewResponse.from(productReview);
+    }
+
+    // 한 상품에 대한 전체 리뷰 조회
+    @Override
+    @Transactional(readOnly = true)
+    public List<ProductReviewResponse> getProductReviewList(Long saleProductId) {
+        List<ProductReview> productReviewList = productReviewRepository.findAllBySaleProductId(saleProductId);
+        return productReviewList.stream()
+                .map(ProductReviewResponse::from)
+                .toList();
+    }
+}

리뷰: `productReviewCreate()`는 `ProductReview` 엔티티를 반환하고 있습니다.
그런데 해당 메서드를 호출하는 곳에서  이 엔티티를 필요로 하고 있지 않아보입니다 !
@@ -0,0 +1,70 @@
+package com.frame2.server.core.board.application;
+
+import com.frame2.server.core.board.domain.ProductReview;
+import com.frame2.server.core.board.infrastructure.ProductReviewRepository;
+import com.frame2.server.core.board.payload.request.ProductReviewModifyRequest;
+import com.frame2.server.core.board.payload.request.ProductReviewRequest;
+import com.frame2.server.core.board.payload.response.ProductReviewResponse;
+import com.frame2.server.core.member.domain.Member;
+import com.frame2.server.core.member.infrastructure.MemberRepository;
+import com.frame2.server.core.order.domain.OrderDetail;
+import com.frame2.server.core.order.infrastructure.OrderDetailRepository;
+import com.frame2.server.core.product.domain.SaleProduct;
+import com.frame2.server.core.product.infrastructure.SaleProductRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+@Transactional
+@RequiredArgsConstructor
+public class ProductReviewServiceImpl implements ProductReviewService {
+
+    private final ProductReviewRepository productReviewRepository;
+    private final SaleProductRepository saleProductRepository;
+    private final MemberRepository memberRepository;
+    private final OrderDetailRepository orderDetailRepository;
+
+    // 생성
+    @Override
+    public void productReviewCreate(ProductReviewRequest request, Long memberId, Long saleProductId) {
+        Member member = memberRepository.findOne(memberId);
+        SaleProduct saleProduct = saleProductRepository.findOne(saleProductId);
+        OrderDetail orderDetail = orderDetailRepository.findOne(request.orderDetailId());
+
+        ProductReview productReview = request.toEntity(member, saleProduct, orderDetail);
+        productReviewRepository.save(productReview);
+    }
+
+    // 수정
+    @Override
+    public void productReviewModify(ProductReviewModifyRequest request, Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).updateProductReview(request.toEntity());
+    }
+
+    // 삭제
+    @Override
+    public void remove(Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).delete();
+    }
+
+    // 단건 조회
+    @Override
+    @Transactional(readOnly = true)
+    public ProductReviewResponse getProductReview(Long productReviewId) {
+        ProductReview productReview = productReviewRepository.findOne(productReviewId);
+        return ProductReviewResponse.from(productReview);
+    }
+
+    // 한 상품에 대한 전체 리뷰 조회
+    @Override
+    @Transactional(readOnly = true)
+    public List<ProductReviewResponse> getProductReviewList(Long saleProductId) {
+        List<ProductReview> productReviewList = productReviewRepository.findAllBySaleProductId(saleProductId);
+        return productReviewList.stream()
+                .map(ProductReviewResponse::from)
+                .toList();
+    }
+}

리뷰: productReviewModify()는 ProductReview 엔티티를 반환하고 있습니다.
그런데 해당 메서드를 호출하는 곳에서 이 엔티티를 필요로 하고 있지 않아보입니다 !
@@ -0,0 +1,70 @@
+package com.frame2.server.core.board.application;
+
+import com.frame2.server.core.board.domain.ProductReview;
+import com.frame2.server.core.board.infrastructure.ProductReviewRepository;
+import com.frame2.server.core.board.payload.request.ProductReviewModifyRequest;
+import com.frame2.server.core.board.payload.request.ProductReviewRequest;
+import com.frame2.server.core.board.payload.response.ProductReviewResponse;
+import com.frame2.server.core.member.domain.Member;
+import com.frame2.server.core.member.infrastructure.MemberRepository;
+import com.frame2.server.core.order.domain.OrderDetail;
+import com.frame2.server.core.order.infrastructure.OrderDetailRepository;
+import com.frame2.server.core.product.domain.SaleProduct;
+import com.frame2.server.core.product.infrastructure.SaleProductRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+@Transactional
+@RequiredArgsConstructor
+public class ProductReviewServiceImpl implements ProductReviewService {
+
+    private final ProductReviewRepository productReviewRepository;
+    private final SaleProductRepository saleProductRepository;
+    private final MemberRepository memberRepository;
+    private final OrderDetailRepository orderDetailRepository;
+
+    // 생성
+    @Override
+    public void productReviewCreate(ProductReviewRequest request, Long memberId, Long saleProductId) {
+        Member member = memberRepository.findOne(memberId);
+        SaleProduct saleProduct = saleProductRepository.findOne(saleProductId);
+        OrderDetail orderDetail = orderDetailRepository.findOne(request.orderDetailId());
+
+        ProductReview productReview = request.toEntity(member, saleProduct, orderDetail);
+        productReviewRepository.save(productReview);
+    }
+
+    // 수정
+    @Override
+    public void productReviewModify(ProductReviewModifyRequest request, Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).updateProductReview(request.toEntity());
+    }
+
+    // 삭제
+    @Override
+    public void remove(Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).delete();
+    }
+
+    // 단건 조회
+    @Override
+    @Transactional(readOnly = true)
+    public ProductReviewResponse getProductReview(Long productReviewId) {
+        ProductReview productReview = productReviewRepository.findOne(productReviewId);
+        return ProductReviewResponse.from(productReview);
+    }
+
+    // 한 상품에 대한 전체 리뷰 조회
+    @Override
+    @Transactional(readOnly = true)
+    public List<ProductReviewResponse> getProductReviewList(Long saleProductId) {
+        List<ProductReview> productReviewList = productReviewRepository.findAllBySaleProductId(saleProductId);
+        return productReviewList.stream()
+                .map(ProductReviewResponse::from)
+                .toList();
+    }
+}

리뷰: productReview와 findProductReview 두 개의 엔티티가 필요할까요?👀
@@ -0,0 +1,70 @@
+package com.frame2.server.core.board.application;
+
+import com.frame2.server.core.board.domain.ProductReview;
+import com.frame2.server.core.board.infrastructure.ProductReviewRepository;
+import com.frame2.server.core.board.payload.request.ProductReviewModifyRequest;
+import com.frame2.server.core.board.payload.request.ProductReviewRequest;
+import com.frame2.server.core.board.payload.response.ProductReviewResponse;
+import com.frame2.server.core.member.domain.Member;
+import com.frame2.server.core.member.infrastructure.MemberRepository;
+import com.frame2.server.core.order.domain.OrderDetail;
+import com.frame2.server.core.order.infrastructure.OrderDetailRepository;
+import com.frame2.server.core.product.domain.SaleProduct;
+import com.frame2.server.core.product.infrastructure.SaleProductRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+@Transactional
+@RequiredArgsConstructor
+public class ProductReviewServiceImpl implements ProductReviewService {
+
+    private final ProductReviewRepository productReviewRepository;
+    private final SaleProductRepository saleProductRepository;
+    private final MemberRepository memberRepository;
+    private final OrderDetailRepository orderDetailRepository;
+
+    // 생성
+    @Override
+    public void productReviewCreate(ProductReviewRequest request, Long memberId, Long saleProductId) {
+        Member member = memberRepository.findOne(memberId);
+        SaleProduct saleProduct = saleProductRepository.findOne(saleProductId);
+        OrderDetail orderDetail = orderDetailRepository.findOne(request.orderDetailId());
+
+        ProductReview productReview = request.toEntity(member, saleProduct, orderDetail);
+        productReviewRepository.save(productReview);
+    }
+
+    // 수정
+    @Override
+    public void productReviewModify(ProductReviewModifyRequest request, Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).updateProductReview(request.toEntity());
+    }
+
+    // 삭제
+    @Override
+    public void remove(Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).delete();
+    }
+
+    // 단건 조회
+    @Override
+    @Transactional(readOnly = true)
+    public ProductReviewResponse getProductReview(Long productReviewId) {
+        ProductReview productReview = productReviewRepository.findOne(productReviewId);
+        return ProductReviewResponse.from(productReview);
+    }
+
+    // 한 상품에 대한 전체 리뷰 조회
+    @Override
+    @Transactional(readOnly = true)
+    public List<ProductReviewResponse> getProductReviewList(Long saleProductId) {
+        List<ProductReview> productReviewList = productReviewRepository.findAllBySaleProductId(saleProductId);
+        return productReviewList.stream()
+                .map(ProductReviewResponse::from)
+                .toList();
+    }
+}

리뷰: 상품 전체 조회는 모든 상품에 대한 모든 리뷰 조회인지, 
아니면 특정 상품에 대한 모든 리뷰 조회인지 궁금합니다 !!
@@ -0,0 +1,70 @@
+package com.frame2.server.core.board.application;
+
+import com.frame2.server.core.board.domain.ProductReview;
+import com.frame2.server.core.board.infrastructure.ProductReviewRepository;
+import com.frame2.server.core.board.payload.request.ProductReviewModifyRequest;
+import com.frame2.server.core.board.payload.request.ProductReviewRequest;
+import com.frame2.server.core.board.payload.response.ProductReviewResponse;
+import com.frame2.server.core.member.domain.Member;
+import com.frame2.server.core.member.infrastructure.MemberRepository;
+import com.frame2.server.core.order.domain.OrderDetail;
+import com.frame2.server.core.order.infrastructure.OrderDetailRepository;
+import com.frame2.server.core.product.domain.SaleProduct;
+import com.frame2.server.core.product.infrastructure.SaleProductRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+@Transactional
+@RequiredArgsConstructor
+public class ProductReviewServiceImpl implements ProductReviewService {
+
+    private final ProductReviewRepository productReviewRepository;
+    private final SaleProductRepository saleProductRepository;
+    private final MemberRepository memberRepository;
+    private final OrderDetailRepository orderDetailRepository;
+
+    // 생성
+    @Override
+    public void productReviewCreate(ProductReviewRequest request, Long memberId, Long saleProductId) {
+        Member member = memberRepository.findOne(memberId);
+        SaleProduct saleProduct = saleProductRepository.findOne(saleProductId);
+        OrderDetail orderDetail = orderDetailRepository.findOne(request.orderDetailId());
+
+        ProductReview productReview = request.toEntity(member, saleProduct, orderDetail);
+        productReviewRepository.save(productReview);
+    }
+
+    // 수정
+    @Override
+    public void productReviewModify(ProductReviewModifyRequest request, Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).updateProductReview(request.toEntity());
+    }
+
+    // 삭제
+    @Override
+    public void remove(Long productReviewId) {
+        productReviewRepository.findOne(productReviewId).delete();
+    }
+
+    // 단건 조회
+    @Override
+    @Transactional(readOnly = true)
+    public ProductReviewResponse getProductReview(Long productReviewId) {
+        ProductReview productReview = productReviewRepository.findOne(productReviewId);
+        return ProductReviewResponse.from(productReview);
+    }
+
+    // 한 상품에 대한 전체 리뷰 조회
+    @Override
+    @Transactional(readOnly = true)
+    public List<ProductReviewResponse> getProductReviewList(Long saleProductId) {
+        List<ProductReview> productReviewList = productReviewRepository.findAllBySaleProductId(saleProductId);
+        return productReviewList.stream()
+                .map(ProductReviewResponse::from)
+                .toList();
+    }
+}

리뷰: 그리고 `ProductReviewModifyRequest` 에서 수정에 필요한 내용을 모두 받고 있는데,
엔티티로 변환할 필요가 있을까요~?
`updateProductReivew()`에서 `ProductReviewModifyRequest` 를 바로 받는 방법도 고려해보면 좋을 것 같아요!