[Spring] 장바구니 기능 추가

김상현·2022년 12월 28일
0

Spring

목록 보기
12/13
post-thumbnail

SHOPPING CART 서비스


0. 장바구니 서비스

쇼핑몰을 이용하는 고객은 상품을 둘러보다가 일단 마음에는 들지만 바로 결제하기 애매한 상품을 보관하거나 2개 이상의 상품을 일괄 결제하기 위해서 장바구니 기능을 자주 이용한다.

이제는 고객의 관심 상품을 보관해 주는 장바구니 기능이 없는 쇼핑몰을 찾아보는 것이 힘들 정도로 보편적인 기능이 되었다.

고객에게 제공할 수 있는 모든 편의 기능 제공을 목표로 하는 나의 쇼핑몰에 장바구니 기능은 반드시 필요한 기능이었고, 며칠간의 고군분투 끝에 기능을 완성할 수 있었다.


1. CartItem 엔티티 설계


CartItem 엔티티의 구성은 그림과 같다.

  • 한명의 고객(Member)이 여러개의 상품(CartItem)을 보관하는 것은 당연하므로 다대일(ManyToOne) 관계를 설정해 주었다.
  • 장바구니에 보관될 상품(CartItem)은 1개의 상품(Item) 정보와 일치하도록 일대일(OneToOne) 관계를 설정해주었다.
  • 해당 상품의 개수를 저장할 count 변수도 선언해주었다.

🧷 CartItem Entity

@Entity
@Getter
@NoArgsConstructor(access = PROTECTED)
public class CartItem extends BaseEntity {

    @Id @GeneratedValue
    @Column(name = "cart_item_id")
    private Long id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @OneToOne(fetch = LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    /** 주문 상품의 개수 **/
    private int count;

    /** CartItem 생성자 **/
    public static CartItem createCartItem(Member member, Item item){
        CartItem cartItem = new CartItem();
        cartItem.member = member;
        cartItem.item = item;
        cartItem.count = 0;
        return cartItem;
    }

	/** 상품의 개수 갱신 **/
    public void addCount(int count){
        this.count += count;
    }
}

2. CartItemRepository


🧷 CartItemRepository

public interface CartItemRepository extends JpaRepository<CartItem, Long> {

    Optional<CartItem> findByMemberIdAndItemId(Long memberId, Long itemId);

    Page<CartItem> findAllByMemberId(Long memberId, Pageable pageable);

    @Modifying
    @Query("delete from CartItem c where c.id in :ids")
    void deleteAllByIds(@Param("ids") List<Long> ids);
}
  • findByMemberIdAndItemId() 는 사용자 아이디(memberId)와 상품 아이디(itemId)를 통해 장바구니 상품(CartItem)을 탐색하는 메서드이다.

  • findAllByMemberId() 는 사용자의 아이디(memberId)에 해당하는 모든 장바구니 상품(CartItem)을 탐색하는 메서드이다. 사용자가 장바구니 페이지에 접근할 경우 해당 메서드를 통해 사용자가 담은 모든 장바구니 상품 정보를 확인할 수 있다.

  • deleteAllByIds() 는 장바구니 상품 아이디(ids)에 해당하는 모든 상품을 삭제하는 메서드이다.


3. CartItemService


🧷 CartItemService

@Service
@Transactional(readOnly = true) // 모든 통신활동 설정을 readOnly = true로 설정한다.
@RequiredArgsConstructor
public class CartService {

    private final CartItemRepository cartItemRepository;
    private final MemberRepository memberRepository;
    private final ItemRepository itemRepository;
    private final OrderService orderService;

    @Transactional
    public Long addCartItem(Long memberId, Long itemId, int count){
        // Member, Item 객체 찾기
        Member member = memberRepository.findById(memberId)
                .orElseThrow(() -> new NotCorrespondingException("CartService/addCartItem : memberId에 해당하는 Member 객체가 존재하지 않습니다."));
        Item item = itemRepository.findById(itemId)
                .orElseThrow(() -> new NotCorrespondingException("CartService/addCartItem : itemId에 해당하는 Item 객체가 존재하지 않습니다."));

        // CartItem 객체 찾기, 존재하지 않을 경우 새로운 CartItem 객체 생성
        CartItem cartItem = cartItemRepository.findByMemberIdAndItemId(memberId, item.getId()).orElseGet(() -> CartItem.createCartItem(member, item));

        // Item 개수 및 Cart 의 Item 개수 갱신
        cartItem.addCount(count);
        cartItemRepository.save(cartItem);
        return cartItem.getId();
    }

    public Page<CartItemDto> cartItems(Long memberId, Pageable pageable){
        return cartItemRepository.findAllByMemberId(memberId, pageable).map(i -> new CartItemDto(i));
    }

    @Transactional
    public void cancelCartItem(Long cartItemId) {
        cartItemRepository.deleteById(cartItemId);
    }

    @Transactional
    public void cancelCartItems(List<Long> cartItemIds) {
        cartItemRepository.deleteAllByIds(cartItemIds);
    }

    @Transactional
    public void order(List<Long> cartItemIds, Long memberId) {
        cartItemRepository.findAllById(cartItemIds)
                .forEach(item -> orderService.order(memberId, item.getItem().getId(), item.getCount()));
    }
}
  • addCartItem() 은 사용자 아이디(memberId)와 상품 아이디(itemId)에 해당하는 장바구니 상품(cartItem)을 탐색한다.
    • 만약 각 아이디에 해당하는 장바구니 상품이 사용자의 장바구니에 존재하지 않는다면 장바구니 상품을 새롭게 만들어서 장바구니에 추가한다.
    • 반대로 각 아이디에 해당하는 장바구니 상품이 사용자의 장바구니에 존재한다면 상품의 개수(count)만 갱신한다.
  • cartItems() 은 현재 사용자의 장바구니에 존재하는 모든 상품 정보를 반환한다.
  • cancelCartItem() 은 장바구니 상품 아이디(cartItemId)에 해당하는 상품을 단건으로 제거한다.
  • cancelCartItems() 은 장바구니 상품 아이디(cartItemIds)에 해당하는 상품 여러 건을 한번에 제거한다.
  • order() 는 장바구니 상품 아이디(cartItemIds)에 해당하는 상품을 주문하는 서비스이다.

4. CartItemController


🧷 CartItemController

@Controller
@RequiredArgsConstructor
public class CartItemController {

    private final CartService cartService;

    @GetMapping("/cartItems")
    public String cartItems(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false)Long memberId,
                            @PageableDefault(size = 8) Pageable pageable,
                            Model model){
        model.addAttribute("items", cartService.cartItems(memberId, pageable));
        return "cart/cartItems";
    }

    @PostMapping("/addItem")
    public String addToCart(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false)Long memberId,
                        @RequestParam("itemId") Long itemId,
                        @RequestParam("count") int count){
        cartService.addCartItem(memberId, itemId, count);
        return "redirect:/cartItems";
    }

    @PostMapping("/cartItem/{cartItemId}/cancel")
    public String cancelCartItem(@PathVariable("cartItemId") Long cartItemId){
        cartService.cancelCartItem(cartItemId);
        return "redirect:/cartItems";
    }

    @PostMapping("/cartItem/order")
    public String orderCartItem(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false)Long memberId,
                                @RequestParam List<Long> cartItemIds){
        cartService.order(cartItemIds, memberId);
        cartService.cancelCartItems(cartItemIds);
        return "redirect:/orders";
    }
}
  • cartItems() 는 세션에 저장된 현재 사용자의 아이디(memberId)를 이용하여 현재 사용자의 장바구니에 담긴 모든 상품의 정보를 model에 담아 화면에 뿌려준다.
  • addToCart() 는 상품 정보창에서 장바구니에 담기 버튼을 클릭했을 때 해당 상품의 아이디(itemId)와 상품의 개수(count) 그리고 세션에 저장된 현재 사용자의 아이디(memberId)를 addCartItem() 메서드의 인자로 전달하여 장바구니 상품 추가 서비스를 실행한다.
  • cancelCartItem() 메서드는 장바구니 상품 아이디(cartItemId)에 해당하는 상품 단건을 제거한다.
  • orderCartItem() 메서드는 장바구니 페이지에서 선택한 상품을 주문 상태로 만들어주는 서비스이다.
    • 주문이 완료된 상품은 장바구니에서 제거한다.

4. 고찰


기존에 있던 서비스를 개선만 해오다가, 추가로 직접 서비스를 구현해 보니 많은 시행착오를 겪었다. cartItem 엔티티의 설계도 최선의 방법으로 설계되어 있는지 잘 모르겠다. 지속적인 개선을 통해 프로젝트가 더 긍정적인 방향으로 나아갈 수 있도록 노력해야겠다.

profile
목적 있는 글쓰기

0개의 댓글