-
Notifications
You must be signed in to change notification settings - Fork 0
20251222 #48 주문시 기본 동작 추가 #54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "20251222_#48_\uC8FC\uBB38\uC2DC_\uAE30\uBCF8_\uB3D9\uC791_\uCD94\uAC00"
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package com.campustable.be.domain.order.controller; | ||
|
|
||
|
|
||
| import com.campustable.be.domain.order.dto.OrderResponse; | ||
| import com.campustable.be.domain.order.service.OrderService; | ||
| import com.campustable.be.global.aop.LogMonitoringInvocation; | ||
| import java.util.List; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.access.prepost.PreAuthorize; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PatchMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/api/orders") | ||
| public class OrderController implements OrderControllerDocs { | ||
|
|
||
| private final OrderService orderService; | ||
|
|
||
| @Override | ||
| @LogMonitoringInvocation | ||
| @PostMapping | ||
| public ResponseEntity<OrderResponse> createOrder() { | ||
| return ResponseEntity.ok(orderService.createOrder()); | ||
| } | ||
|
|
||
| @Override | ||
| @LogMonitoringInvocation | ||
| @PatchMapping("/{orderId}/categories/{categoryId}/ready") | ||
| @PreAuthorize("hasRole('ROLE_ADMIN')") | ||
| public ResponseEntity<Void> updateCategoryToReady( | ||
| @PathVariable Long orderId, | ||
| @PathVariable Long categoryId) { | ||
| orderService.updateCategoryToReady(orderId,categoryId); | ||
| return ResponseEntity.ok().build(); | ||
| } | ||
|
|
||
| @Override | ||
| @LogMonitoringInvocation | ||
| @PatchMapping("/{orderId}/categories/{categoryId}/complete") | ||
| @PreAuthorize("hasRole('ROLE_ADMIN')") | ||
| public ResponseEntity<Void> updateCategoryToComplete( | ||
| @PathVariable Long orderId, | ||
| @PathVariable Long categoryId | ||
| ) { | ||
| orderService.updateCategoryToComplete(orderId,categoryId); | ||
| return ResponseEntity.ok().build(); | ||
| } | ||
|
|
||
| @Override | ||
| @LogMonitoringInvocation | ||
| @GetMapping | ||
| public ResponseEntity<List<OrderResponse>> getMyOrders() { | ||
| return ResponseEntity.ok(orderService.getMyOrders()); | ||
| } | ||
|
Comment on lines
+57
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 페이지네이션 누락으로 인한 성능 저하 가능성
🔎 페이지네이션 구현 제안 @Override
@LogMonitoringInvocation
@GetMapping
-public ResponseEntity<List<OrderResponse>> getMyOrders() {
- return ResponseEntity.ok(orderService.getMyOrders());
+public ResponseEntity<Page<OrderResponse>> getMyOrders(Pageable pageable) {
+ return ResponseEntity.ok(orderService.getMyOrders(pageable));
}OrderService와 Repository도 함께 수정 필요: // Repository
Page<Order> findByUserUserIdOrderByCreatedAtDesc(Long userId, Pageable pageable);
// Service
Page<OrderResponse> getMyOrders(Pageable pageable) {
// ... implementation
}
🤖 Prompt for AI Agents |
||
|
|
||
| @Override | ||
| @GetMapping("/users/{userId}") | ||
| @PreAuthorize("hasRole('ROLE_ADMIN')") | ||
| public ResponseEntity<List<OrderResponse>> getOrdersByUserId(@PathVariable Long userId) { | ||
| return ResponseEntity.ok(orderService.getOrdersByUserId(userId)); | ||
| } | ||
|
Comment on lines
+63
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 관리자 조회 엔드포인트에도 페이지네이션 필요
페이지네이션 적용을 권장합니다. |
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| package com.campustable.be.domain.order.controller; | ||
|
|
||
| import com.campustable.be.domain.order.dto.OrderResponse; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.Parameter; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import java.util.List; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
|
|
||
| @Tag(name = "Order API", description = "주문 관련 API (생성, 조회, 상태 변경)") | ||
| public interface OrderControllerDocs { | ||
|
|
||
| @Operation(summary = "주문 생성", description = "현재 사용자의 장바구니에 담긴 메뉴들로 주문을 생성합니다.") | ||
| @ApiResponses(value = { | ||
| @ApiResponse(responseCode = "200", description = "주문 생성 성공"), | ||
| @ApiResponse(responseCode = "400", description = "재고 부족"), | ||
| @ApiResponse(responseCode = "404", description = "장바구니가 비어있거나 사용자를 찾을 수 없음") | ||
| }) | ||
| ResponseEntity<OrderResponse> createOrder(); | ||
|
|
||
| @Operation(summary = "카테고리별 조리 완료 처리 (관리자)", description = "특정 주문(OrderId) 내의 특정 카테고리(CategoryId) 메뉴들을 모두 '조리 완료'로 변경합니다.") | ||
| ResponseEntity<Void> updateCategoryToReady( | ||
| @Parameter(description = "주문 ID", example = "1") @PathVariable Long orderId, | ||
| @Parameter(description = "카테고리 ID", example = "2") @PathVariable Long categoryId | ||
| ); | ||
|
|
||
| @Operation(summary = "카테고리별 수령 완료 처리 (관리자)", description = "특정 주문(OrderId) 내의 특정 카테고리(CategoryId) 메뉴들을 모두 '수령 완료'로 변경합니다.") | ||
| ResponseEntity<Void> updateCategoryToComplete( | ||
| @Parameter(description = "주문 ID", example = "1") @PathVariable Long orderId, | ||
| @Parameter(description = "카테고리 ID", example = "2") @PathVariable Long categoryId | ||
| ); | ||
|
|
||
| @Operation(summary = "내 주문 내역 조회", description = "사용자의 주문 내역을 조회합니다. (진행 중인 주문이 상단에 노출됨)") | ||
| @ApiResponse(responseCode = "200", description = "조회 성공") | ||
| ResponseEntity<List<OrderResponse>> getMyOrders(); | ||
|
|
||
| @Operation(summary = "특정 유저 주문 조회 (관리자)", description = "관리자가 특정 유저(User ID)의 모든 주문 내역을 조회합니다.") | ||
| @ApiResponses(value = { | ||
| @ApiResponse(responseCode = "200", description = "조회 성공"), | ||
| @ApiResponse(responseCode = "403", description = "관리자 권한 없음"), | ||
| @ApiResponse(responseCode = "404", description = "해당 유저를 찾을 수 없음") | ||
| }) | ||
| ResponseEntity<List<OrderResponse>> getOrdersByUserId( | ||
| @Parameter(description = "조회할 유저 ID", example = "5") | ||
| @PathVariable Long userId | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||
| package com.campustable.be.domain.order.dto; | ||||
|
|
||||
| import com.campustable.be.domain.order.entity.OrderItem; | ||||
| import com.campustable.be.domain.order.entity.OrderStatus; | ||||
| import lombok.AccessLevel; | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용되지 않는 import를 제거하세요.
🔎 수정 제안-import lombok.AccessLevel;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||
| import lombok.Getter; | ||||
|
|
||||
| @Getter | ||||
| public class OrderItemDto { | ||||
|
|
||||
| private Long orderItemId; | ||||
| private Long menuId; | ||||
| private String menuName; | ||||
| private int orderPrice; | ||||
| private int quantity; | ||||
| private int subTotal; | ||||
| private String menuUrl; | ||||
| private OrderStatus status; | ||||
| private Long categoryId; | ||||
|
|
||||
| public OrderItemDto(OrderItem orderItem) { | ||||
| this.orderItemId = orderItem.getOrderItemId(); | ||||
| this.menuId = orderItem.getMenu().getId(); | ||||
| this.menuName = orderItem.getMenu().getMenuName(); | ||||
| this.orderPrice = orderItem.getOrderPrice(); //주문 당시가격 | ||||
| this.quantity = orderItem.getQuantity(); | ||||
| this.subTotal = orderItem.getOrderPrice() * orderItem.getQuantity(); | ||||
| this.menuUrl = orderItem.getMenu().getMenuUrl(); | ||||
| this.status = orderItem.getStatus(); | ||||
| this.categoryId=orderItem.getCategoryId(); | ||||
| } | ||||
|
Comment on lines
+21
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -type f -name "Menu.java" | head -20Repository: CampusTable/campus-table-be Length of output: 134 🏁 Script executed: find . -type f -name "OrderItem.java" | head -20Repository: CampusTable/campus-table-be Length of output: 140 🏁 Script executed: git ls-files | grep -E "(Menu|OrderItem)\.java$"Repository: CampusTable/campus-table-be Length of output: 200 🏁 Script executed: wc -l ./src/main/java/com/campustable/be/domain/menu/entity/Menu.javaRepository: CampusTable/campus-table-be Length of output: 137 🏁 Script executed: cat -n ./src/main/java/com/campustable/be/domain/menu/entity/Menu.javaRepository: CampusTable/campus-table-be Length of output: 3303 🏁 Script executed: wc -l ./src/main/java/com/campustable/be/domain/order/entity/OrderItem.javaRepository: CampusTable/campus-table-be Length of output: 143 🏁 Script executed: cat -n ./src/main/java/com/campustable/be/domain/order/entity/OrderItem.javaRepository: CampusTable/campus-table-be Length of output: 2596 OrderItem의 getTotalPrice() 메서드를 재사용하세요.
- this.subTotal = orderItem.getOrderPrice() * orderItem.getQuantity();
+ this.subTotal = orderItem.getTotalPrice();🤖 Prompt for AI Agents |
||||
|
|
||||
| } | ||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package com.campustable.be.domain.order.dto; | ||
|
|
||
| import com.campustable.be.domain.order.entity.Order; | ||
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| @Builder | ||
| @AllArgsConstructor // Builder와 함께 사용 시 관례적으로 추가 | ||
| public class OrderResponse { | ||
|
|
||
| private Long orderId; | ||
| private int totalPrice; | ||
| private LocalDateTime orderDate; | ||
| private List<OrderItemDto> orderItems; | ||
|
|
||
| public static OrderResponse from(Order order) { | ||
| return OrderResponse.builder() | ||
| .orderId(order.getOrderId()) | ||
| .totalPrice(order.getTotalPrice()) | ||
| .orderDate(order.getCreatedAt()) // BaseTimeEntity의 생성일 | ||
| .orderItems(order.getOrderItems().stream() | ||
| .map(OrderItemDto::new) | ||
| .toList()) | ||
| .build(); | ||
| } | ||
|
Comment on lines
+20
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find src -type f -name "OrderRepository.java" | head -5Repository: CampusTable/campus-table-be Length of output: 148 🏁 Script executed: find src -type f -name "OrderItemDto.java" | head -5Repository: CampusTable/campus-table-be Length of output: 138 🏁 Script executed: find src -type f -name "Order.java" -o -name "OrderItem.java" | grep -E "(Order\.java|OrderItem\.java)$" | head -10Repository: CampusTable/campus-table-be Length of output: 202 🏁 Script executed: git ls-files src/main/java/com/campustable/be/domain/order | head -20Repository: CampusTable/campus-table-be Length of output: 801 🏁 Script executed: cat -n src/main/java/com/campustable/be/domain/order/repository/OrderRepository.javaRepository: CampusTable/campus-table-be Length of output: 506 🏁 Script executed: cat -n src/main/java/com/campustable/be/domain/order/dto/OrderItemDto.javaRepository: CampusTable/campus-table-be Length of output: 1309 🏁 Script executed: cat -n src/main/java/com/campustable/be/domain/order/entity/Order.javaRepository: CampusTable/campus-table-be Length of output: 2299 🏁 Script executed: cat -n src/main/java/com/campustable/be/domain/order/entity/OrderItem.javaRepository: CampusTable/campus-table-be Length of output: 2596 🏁 Script executed: rg -n "OrderResponse.from\(" src/main/java/com/campustable/be --type java -B 2 -A 2Repository: CampusTable/campus-table-be Length of output: 1049 🏁 Script executed: rg -n "hibernate.show_sql|show-sql|format_sql" src/main/resources -t properties -t yamlRepository: CampusTable/campus-table-be Length of output: 105 🏁 Script executed: cat -n src/main/java/com/campustable/be/domain/order/service/OrderService.javaRepository: CampusTable/campus-table-be Length of output: 4631 🏁 Script executed: find src/main/resources -type f \( -name "application*.properties" -o -name "application*.yml" \)Repository: CampusTable/campus-table-be Length of output: 105 🏁 Script executed: cat -n src/main/resources/application.ymlRepository: CampusTable/campus-table-be Length of output: 1195 주문 조회 시 N+1 쿼리 발생 가능
OrderRepository의 @EntityGraph(attributePaths = {"orderItems", "orderItems.menu"})
List<Order> findByUserUserIdOrderByCreatedAtDesc(Long userId);또는 JPQL 사용: @Query("SELECT o FROM Order o " +
"JOIN FETCH o.orderItems oi " +
"JOIN FETCH oi.menu " +
"WHERE o.user.userId = :userId " +
"ORDER BY o.createdAt DESC")
List<Order> findByUserUserIdOrderByCreatedAtDesc(Long userId);🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package com.campustable.be.domain.order.entity; | ||
|
|
||
| import com.campustable.be.domain.user.entity.User; | ||
| import com.campustable.be.global.common.BaseTimeEntity; | ||
| import com.campustable.be.global.exception.CustomException; | ||
| import com.campustable.be.global.exception.ErrorCode; | ||
| import jakarta.persistence.CascadeType; | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.EnumType; | ||
| import jakarta.persistence.Enumerated; | ||
| import jakarta.persistence.FetchType; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.JoinColumn; | ||
| import jakarta.persistence.ManyToOne; | ||
| import jakarta.persistence.OneToMany; | ||
| import jakarta.persistence.Table; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.Setter; | ||
|
|
||
| @Entity | ||
| @Getter | ||
| @Setter | ||
| @NoArgsConstructor | ||
| @Table(name="orders") | ||
| public class Order extends BaseTimeEntity { | ||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| @Column(name="order_id") | ||
| private Long orderId; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "user_id") | ||
| private User user; | ||
|
|
||
| @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) | ||
| private List<OrderItem> orderItems = new ArrayList<>(); | ||
|
Comment on lines
+41
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's locate and examine the Order.java file
find . -name "Order.java" -path "*/domain/order/entity/*"Repository: CampusTable/campus-table-be Length of output: 136 🏁 Script executed: # Read the Order.java file to verify the cascade configuration
cat -n src/main/java/com/campustable/be/domain/order/entity/Order.java | head -60Repository: CampusTable/campus-table-be Length of output: 2282 🏁 Script executed: # Check for soft delete patterns in the codebase
rg -n "deleted|DeletedDate|@SQLDelete" --type java -iRepository: CampusTable/campus-table-be Length of output: 53 🏁 Script executed: # Check for audit-related annotations and listeners
rg -n "@Audited|@EntityListeners|AuditingEntityListener|@CreatedDate|@LastModifiedDate" --type javaRepository: CampusTable/campus-table-be Length of output: 601 🏁 Script executed: # Check CascadeType patterns in other entities
rg -n "CascadeType\.(ALL|REMOVE|PERSIST|MERGE)" --type java -B 1 -A 1Repository: CampusTable/campus-table-be Length of output: 1347 주문 항목 삭제 전략 재검토 필요
다른 엔티티들(User-Cart, Cart-CartItems)과의 일관성을 위해 🤖 Prompt for AI Agents |
||
|
|
||
| private int totalPrice; | ||
|
|
||
|
|
||
| public static Order createOrder(User user, List<OrderItem> orderItems) { | ||
| Order order = new Order(); | ||
| order.setUser(user); | ||
| for (OrderItem orderItem : orderItems) { | ||
| order.addOrderItem(orderItem); | ||
| } | ||
| order.setTotalPrice(orderItems.stream().mapToInt(OrderItem::getTotalPrice).sum()); | ||
| return order; | ||
| } | ||
|
|
||
| public void addOrderItem(OrderItem orderItem) { | ||
| orderItems.add(orderItem); | ||
| orderItem.setOrder(this); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| package com.campustable.be.domain.order.entity; | ||
|
|
||
|
|
||
| import com.campustable.be.domain.menu.entity.Menu; | ||
| import com.campustable.be.global.exception.CustomException; | ||
| import com.campustable.be.global.exception.ErrorCode; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.EnumType; | ||
| import jakarta.persistence.Enumerated; | ||
| import jakarta.persistence.FetchType; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.JoinColumn; | ||
| import jakarta.persistence.ManyToOne; | ||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| @Entity | ||
| public class OrderItem { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long orderItemId; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name="order_id") | ||
| private Order order; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name="menu_id") | ||
| private Menu menu; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| private OrderStatus status; | ||
|
|
||
| private String menuName; | ||
| private int orderPrice; | ||
| private int quantity; | ||
|
|
||
| private Long categoryId; | ||
|
|
||
| public static OrderItem createOrderItem(Menu menu, int orderPrice, int quantity) { | ||
| OrderItem orderItem = new OrderItem(); | ||
| orderItem.setMenu(menu); | ||
| orderItem.setStatus(OrderStatus.PREPARING); | ||
| orderItem.setMenuName(menu.getMenuName()); | ||
| orderItem.setOrderPrice(orderPrice); | ||
| orderItem.setQuantity(quantity); | ||
| orderItem.setCategoryId(menu.getCategory().getCategoryId()); | ||
| return orderItem; | ||
| } | ||
|
|
||
| public int getTotalPrice() { | ||
| return getOrderPrice() * getQuantity(); | ||
| } | ||
|
|
||
| public void markAsReady() { | ||
| if (this.status != OrderStatus.PREPARING) { | ||
| throw new CustomException(ErrorCode.INVALID_ORDER_STATUS); | ||
| } | ||
| this.status = OrderStatus.READY; | ||
| } | ||
|
|
||
| // 수령 대기 -> 주문 완료 | ||
| public void markAsCompleted() { | ||
| if (this.status != OrderStatus.READY) { | ||
| throw new CustomException(ErrorCode.INVALID_ORDER_STATUS); | ||
| } | ||
| this.status = OrderStatus.COMPLETED; | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.campustable.be.domain.order.entity; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum OrderStatus { | ||
|
|
||
| PREPARING("조리 중"), | ||
|
|
||
| READY("수령 대기"), | ||
|
|
||
| COMPLETED("주문 완료"); | ||
|
|
||
| private final String description; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.campustable.be.domain.order.repository; | ||
|
|
||
| import com.campustable.be.domain.order.entity.OrderItem; | ||
| import java.util.List; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface OrderItemRepository extends JpaRepository<OrderItem, Long> { | ||
|
|
||
| List<OrderItem> findByOrderOrderIdAndCategoryId(Long orderId, Long categoryId); | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.campustable.be.domain.order.repository; | ||
|
|
||
| import com.campustable.be.domain.order.entity.Order; | ||
| import java.util.Collection; | ||
| import java.util.List; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface OrderRepository extends JpaRepository<Order, Long> { | ||
|
|
||
| List<Order> findByUserUserIdOrderByCreatedAtDesc(Long userId); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
동시성 제어 누락으로 인한 재고 오버셀링 위험
decreaseStockQuantity메서드는 동기화 메커니즘 없이 재고를 감소시킵니다. 여러 주문이 동시에 발생하면 check-then-act 경합 상태가 발생하여 재고가 음수가 될 수 있습니다.예시:
stockQuantity - quantity >= 0체크 통과🔎 동시성 제어 방법 제안
방법 1 (권장): 비관적 락 사용
OrderService에서 Menu 조회 시 락 적용:
방법 2: 낙관적 락 사용
Menu 엔티티에 버전 필드 추가:
public class Menu { + @Version + private Long version;방법 3: 데이터베이스 제약 조건