주문하기는 모든 테이블과 연관되어있어서 마지막으로 남겨뒀다.
주문하기 테이블 설계에 대한 고민
1. 장바구니를 구현한것과 동일하게 구현
Cart - CartItem 테이블을 만들어서 장바구니 기능을 구현한 것 처럼
Order - OrderItem 테이블을 만드는 방법.
'주문하기' 버튼을 누르면 Order-OrderItem 테이블에 정보가 저장되고
이를 결제 전 최종확인 단계에서 볼 수 있음.
결제가 완료되면 결제여부를 알려주는 컬럼의 값이 변경됨.
But 나는 Order 테이블에 결제 완료된 애들만 저장하고 싶은데 ? => 일단보류.
2. 상품번호를 배열에 저장해 파라미터로 넘기는 방법
결제 전 최종확인 단계에서 주문정보를 보여줘야 함.
이 때 보여줄 정보는 상품번호를 배열로 받아서 findById 메서드를 이용해 List<DTO> 형태로 view 로 보내서
사용자가 결제 전 주문정보를 확인할 수 있도록 함.
결제가 완료되면 Order 테이블에 저장.
그럼 결제 시
컨트롤러에 또 상품번호 배열을 파라미터로 넘겨서 findById 메서드를 이용해서 DB에 저장해야하는건가 ?
=> 같은 동작이 반복됨. 탐탁치 않음.
결제하기 동작 실행은 1번과 동일하게 진행됨. 차라리 1번 방법으로 가는게 낮겠음.
※ Entity 설계
Order
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "orders") //중요 !
@Entity
public class Order extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name ="order_id")
private Long id; //주문번호
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@Enumerated(EnumType.STRING)
private OrderStatus orderStatus = OrderStatus.CANCEL; //주문상태
@OneToMany(mappedBy = "order",fetch = FetchType.LAZY)
private List<OrderItem> orderItems = new ArrayList<>();
private int totalPrice;
@Builder
public Order(User user, OrderStatus orderStatus,List<OrderItem>orderItems,int totalPrice) {
this.user = user;
this.orderStatus = orderStatus;
this.orderItems=orderItems;
this.totalPrice = totalPrice;
}
}
OrderItem
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "order_item")
@Entity
public class OrderItem extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name ="order_item_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "item_id")
private Item item;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="order_id")
private Order order;
@Builder
public OrderItem(Long id,Item item, Order order) {
this.id = id;
this.item = item;
this.order = order;
}
}
※ 단일상품주문
Service
//단일상품주문
@Transactional
public Long order(Long itemId,String email){
//orderItem 객체생성
List<OrderItem> orderItems = new ArrayList<>();
Item item = itemRepository.findItemById(itemId);
log.info("itmeId = {}",itemId);
OrderItemDto orderItemDto = OrderItemDto.builder()
.item(item)
.build();
orderItems.add(orderItemDto.toEntity());
//order 객체생성
User user = userRepository.findByEmail(email);
log.info("userName = {}",user.getName());
OrderDto orderDto = OrderDto.builder()
.user(user)
.orderStatus(OrderStatus.CANCEL)
.orderItems(orderItems)
.build();
//order 객체 db저장 (Cascade로 인해 OrderItem 객체도 같이 저장)
Order order = orderRepository.save(orderDto.toEntity());
//item SellStatus 변경
item.updateSellStatus(SellStatus.SOLD_OUT);
itemRepository.save(item);
log.info("orderId = {}",order.getId());
return order.getId();
}
메서드를 너무 한개에 때려넣었나 .. 싶어서 장바구니 상품 주문 메서드 구현할때는 좀 쪼갰다 ㅎㅅㅎ
※ 장바구니 상품주문
오류 : TransientPropertyValueException
object references an unsaved transient instance - save the transient instance before flushing
JPA 관련 Hibernate 에러: object references an unsaved transient instance - save the transient instance before flushing
에러 로그 TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing 원인 JPA 연관 관계 테스트 중에 발생했습니다. FK 로 사용되는..
bcp0109.tistory.com
위 블로그에 따르면 해당 에러는
FK로 사용되는 컬럼값이 없는 상태에서 데이터를 넣으려다 발생한 에러 ! 라고한다
작성한 Service 코드를 뜯어보자
//장바구니 상품을 주문상품에 넣어주는 메서드
@Transactional
public OrderItemDto addOrderItem(Item item,Order order){
OrderItemDto orderItemDto = OrderItemDto.builder()
.item(item)
.order(order)
.build();
orderItemRepository.save(orderItemDto.toEntity());
return orderItemDto;
}
//주문상품 목록을 주문에 넣어주는 메서드
@Transactional
public void addOrder(List<OrderItem> orderItems,Order order){
order = Order.builder()
.orderItems(orderItems)
.build();
orderRepository.save(order);
}
//장바구니 상품 전체 주문
@Transactional
public int orders(Long userId){
User user = userRepository.findById(userId).orElseThrow(EntityNotFoundException::new);
//User정보를 가진 Order 객체 생성
Order order = Order.createOrder(user);
int result = 0;
//user의 장바구니와 장바구니 속 상품 목록 가져오기
Cart cart = cartService.findUserCart(user.getId());
List<CartItemDto> cartItems = cartService.getCartItemList(user.getId());
int totalPrice = 0;
List<OrderItem> orderItems = new ArrayList<>();
for(CartItemDto cartItemDto : cartItems){
// 이미 판매된 상품이 장바구니에 포함되어있으면 result = 1
if(cartItemDto.getItem().getSellStatus()!=SellStatus.SELL){
result= 1;
return result;
}
totalPrice += cartItemDto.getItem().getPrice();
//Item 을 주문상품(OrderItem) 에 넣어줌
OrderItemDto orderItemDto = addOrderItem(cartItemDto.getItem(), order);
orderItems.add(orderItemDto.toEntity()); //주문상품들을 주문상품 목록에 추가
}
//생성된 order 객체에 orderItems 정보 넣기
addOrder(orderItems,order);
return result;
}
오류 1. [장바구니 상품을 주문상품에 넣어주는 메서드] addOrderItem
리턴값은 id가 아직 생성되지않은 orderItemDto 가 아니라
orderItemDto 를 save 하고 반환된 orderItem 객체를 리턴하는게 옳다.
//수정전
@Transactional
public OrderItemDto addOrderItem(Item item,Order order){
OrderItemDto orderItemDto = OrderItemDto.builder()
.item(item)
.order(order)
.build();
orderItemRepository.save(orderItemDto.toEntity());
return orderItemDto;
}
//수정후
@Transactional
public OrderItem addOrderItem(Item item,Order order){
OrderItemDto orderItemDto = OrderItemDto.builder()
.item(item)
.order(order)
.build();
OrderItem orderItem = orderItemRepository.save(orderItemDto.toEntity());
return orderItem;
}
오류 2. createOrder 메서드
Order 객체만 생성하고 save 를 안해주니 fk 가 생성되지 않아서 오류가 발생했다
정확히는 이 부분에서 오류가 난건데
OrderItemDto orderItemDto = addOrderItem(cartItemDto.getItem(), order);
addOrder(orderItems,order);
이때 order 객체는 id 를 가지고있지않기 때문에 addOrderItem에서
OrderItem 을 save 하는 메서드에서 order 의 fk 가 없어서 오류가 발생한 것이다.
수정 전 createOrder
public static Order createOrder(User user) {
Order order = new Order();
OrderDto orderDto = OrderDto.builder()
.user(user)
.build();
return orderDto.toEntity();
}
(다 지워서 잘 기억안나는데 이런식으로 만들었었던것같음)
수정 후
public Order createOrder(User user){
OrderDto orderDto = OrderDto.builder()
.user(user)
.build();
return orderRepository.save(orderDto.toEntity());
}
결과 : 실패
사유 : insert 쿼리가 두번날라감

JPA 의 DirtyChecking 을 통해 save 메서드를 이용하면
변경을 감지하여 자동으로 update 쿼리가 날라가는 것이 아닌가 ?
첫번째 save() = insert
두번째 save() = update
쿼리가 보내졌어야하는데 왜 두번 다 insert 로 처리되었을까 ? 테스트 코드 작성을 통해 확인해보자
@Test
@DisplayName("주문정보 입력테스트")
void test2(){
//given
Long userId = 3L;
//when
int result = orderService.orders(userId);
//then
assertEquals(1L, orderRepository.count());



확실히 insert 쿼리가 두번 날라가서 expected result 갯수가 다르다.
save() 는 id로 insert / update 를 결정한다.
두번째 save() 시 builder 로 id 를 값을 넣고, update 될 다른 값들을 추가로 넣어줬다.
그렇다면 builder 에서 id 가 제대로 빌드되지않아 insert 가 두번된걸까 ?
==> YES !!
OrderDto Builder에 id를 누락했었다.
id 가 빌드되지않았으니 없는 객체로 인식하여 insert 쿼리를 날리고 있었던 것이다.
해서 수정함.
public Order toEntity(){
return Order.builder()
.id(id)
.user(user)
.orderItems(orderItems)
.orderStatus(orderStatus)
.totalPrice(totalPrice)
.build();
}
@Builder
public OrderDto(Long id, OrderStatus orderStatus,User user, List<OrderItem> orderItems,int totalPrice){
this.id = id;
this.user = user;
this.orderStatus = orderStatus;
this.orderItems = orderItems;
this.totalPrice = totalPrice;
}
//주문정보 입력해주는 메서드
@Transactional
public Order addOrderInfo(List<OrderItem> orderItems,Long orderId,int totalPrice){
Order order = orderRepository.findOrderById(orderId);
OrderDto orderDto = OrderDto.builder()
.id(order.getId())
.user(order.getUser())
.orderItems(orderItems)
.orderStatus(OrderStatus.CANCEL)
.totalPrice(totalPrice)
.build();
return orderRepository.save(orderDto.toEntity());
}

TEST 성공 ㅎㅎ
※ 마지막으로 구매목록에서 상품명 (00외 N개) 설정하기

OrderDto 에 주문 이름 (00외 n개) 멤버 추가
뷰에서만 확인하면 되는 정보이기 때문에 entity 에는 따로 주가 하지 않고
뷰에서 응답받는 OrderDto 에만 멤버변수 추가해줌
private String orderName;
각 주문의 상품개수를 조회하여 orderName을 만들어
orderDto 로 리턴해주는 메서드 selectOrderList 생성 (진짜 재밌게 만든 메서드 ㅎㅎ)
//사용자의 주문목록 조회
@Transactional
public List<OrderDto> selectOrderList(Long userId){
List<Order> orderList = orderRepository.findOrdersByUser_IdAndOrderStatus(userId, OrderStatus.ORDER);
List <OrderDto> orderDtoList = new ArrayList<>();
int itemQty = 0;
String orderName = "";
OrderDto orderDto = new OrderDto();
for(Order order : orderList){ //사용자의 주문갯수만큼
List<OrderItem> orderItemList = order.getOrderItems();
for(OrderItem orderItem : orderItemList){ //주문1개의 상품개수만큼
itemQty ++;
orderName = orderItem.getItem().getItemName();
}
if(itemQty>1){
itemQty -= 1;
orderName = orderName + " 외 " + itemQty +"개";
}
orderDto = OrderDto.builder()
.id(order.getId())
.orderName(orderName)
.totalPrice(order.getTotalPrice())
.regTime(order.getRegTime())
.build();
orderDtoList.add(orderDto);
}
return orderDtoList;
}
'프로젝트' 카테고리의 다른 글
| [SpringBoot & JPA 프로젝트] 배포 후 실제 웹서비스 운영 단계로 나아가기 (0) | 2022.09.28 |
|---|---|
| [SpringBoot & JPA 프로젝트] 인터셉터 / 404,500 error page (0) | 2022.09.28 |
| [SpringBoot & JPA 프로젝트] 장바구니에 상품 추가하기 / 삭제하기 (0) | 2022.08.25 |
| [SpringBoot & JPA 프로젝트] 판매자 : 상품삭제하기 (delFlag) (0) | 2022.08.24 |
| [Spring Boot & JPA 프로젝트] 상품등록 / 파일업로드(다중X) (0) | 2022.08.17 |