프로젝트

[SpringBoot & JPA 프로젝트] 장바구니에 상품 추가하기 / 삭제하기

sian han 2022. 8. 25. 04:02

과제 : '장바구니' 버튼 클릭 시 해당 상품이 장바구니에 들어감

  내가 만들 장바구니는

  '세상에 단하나 존재하는 뜨개상품' 을 판매하는 마켓 특성상

  장바구니에 담길 상품의 수량을 선택하지않아도 된다

 

 

모든 사용자는 1개의 장바구니 (Cart) 를 가지고,

장바구니 안에는 Item들을 넣을 수 있다.

장바구니 안에 들어가는 Item 들을 CartItem 라고 하기로 했다

 

Cart 와 User 를 OneToOne 연관관계를 맺어주고

Cart는 List<CartItem> 을 변수로 갖으며, 둘은 OneToMany 로 연관관계를 맺어주었다.

 

위 내용을 좀 더 직관적으로 바라보기 위해서 그림으로 그려봤다

 

 

 

 


 

Cart

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "cart")
@Entity
public class Cart extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name ="cart_id")
    private Long id;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    @Nullable
    private User user;

    @OneToMany(mappedBy = "id",cascade = CascadeType.ALL)
    @Nullable
    private List<CartItem> cartItemList = new ArrayList<>();

    @Builder
    public Cart(User user,List<CartItem> cartItemList){
        this.user = user;
        this.cartItemList = cartItemList;
    }
}

 

 

CartItem

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

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cart_item_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "cart_id")
    private Cart cart;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    @Builder
    public CartItem(Long id,Cart cart, Item item){
        this.id=id;
        this.cart = cart;
        this.item = item;
    }
}

 

 

CartController

  //장바구니 추가
    @GetMapping(value ="/cart/{itemId}")
    public String cart_add_get(@PathVariable("itemId")Long itemId, HttpSession httpSession, Model model){
        Long userId = (Long) httpSession.getAttribute("id");

        log.info("CartController");

        Cart cart = cartService.save(userId, itemId);

        String msg ="장바구니에 추가되었습니다", url ="";
        model.addAttribute("url",url);
        model.addAttribute("msg",msg);
        return "/common/message";
    }

 

 

CartService

@Slf4j
@RequiredArgsConstructor
@Service
public class CartService {
    private final CartRepository cartRepository;
    private final CartItemRepository cartItemRepository;
    private final UserRepository userRepository;
    private final ItemRepository itemRepository;

    //cart 만들기 (member 만 넣어서)
    //근데이제 이미  cart 가 있으면 안만듦
    @Transactional
    public Cart save(Long userId, Long itemId) {
        User user = userRepository.findById(userId).orElseThrow(EntityNotFoundException::new);

        CartDto cartDto = CartDto.builder()
                .user(user)
                .build();
        Cart cart = cartRepository.save(cartDto.toEntity());

        //cartItemList가 이미있는지 없는지 확인해야함
        List<CartItem> cartItemList = cart.getCartItemList();

        //item 받아서 cartItem 만들기
        Item item = itemRepository.findItemById(itemId);
        log.info("item= {}",item.getId()); //item= 34

        CartItemDto cartItemDto = CartItemDto.builder()
                .cart(cart)
                .item(item)
                .build();

        log.info("cartItem_id={}",cartItemDto.getId()); //cartItem_id=null

        //cartItem 만든거 cartItemList 에 넣기
        CartItem cartItem = cartItemRepository.save(cartItemDto.toEntity());

        //만든 리스트 만들어놓은 cart에 add하기
        cartItemList.add(cartItem);

        return cart;
    }
}

 

 

 

 

 

 

여기서 NullPointerException 이 발생한다.

 

Service 코드를 다시 뜯어보자

 

1. 로그인 한 User 의 장바구니를 생성한다

2. CartItem 을 담는 List<CartItem> 를 생성한다

3. save 메서드의 인자로 받은 itemId 를 이용해서 item 객체를 불러옴

4. cart 와 item 객체를 넣어서 CartItemDto 를 build

5. build 한 CartItemDto를 save 하고, CartItemList 에 CartItem 을 add

 

 

 

 

오류발견

List<CartItem> cartItemList = cart.getCartItemList();

장바구니(Cart) 에 저장된 상품(CartItem) 이 없으면 cartItemList 가 null 일 수 밖에 없으므로

NullPointerException 이 발생했다

 

그리고

 CartDto cartDto = CartDto.builder()
                .user(user)
                .build();
        Cart cart = cartRepository.save(cartDto.toEntity());  
  .
  .
 cartItemList.add(cartItem);
 return cart;

 

리턴된 cart 는 상품목록(cartItemList) 를 포함하고 있지않다

장바구니(cart) 에상품을 추가(add) 한 상품목록(cartItemList) 이 추가되도록 다시 빌드해주는 작업이 생략되었다

 

 

 

 

오류수정

Service

@Transactional
    public Cart save(Long userId, Long itemId) {
        User user = userRepository.findById(userId).orElseThrow(EntityNotFoundException::new);

        CartDto cartDto = CartDto.builder()
                .user(user)
                .build();
        Cart cart = cartRepository.save(cartDto.toEntity());
        Item item = itemRepository.findItemById(itemId);

        CartItemDto cartItemDto = CartItemDto.builder()
                .cart(cart)
                .item(item)
                .build();

        CartItem cartItem = cartItemRepository.save(cartItemDto.toEntity());
        List<CartItem> cartItemList = new ArrayList<>();
        cartItemList.add(cartItem);
        CartDto cartDto1 = cartDto.builder()
                .cartItemList(cartItemList)
                .user(cart.getUser())
                .id(cart.getId())
                .build();

        Cart cart1 = cartRepository.save(cartDto1.toEntity());
        return cart;
    }

 

 

 

 

수정된 save 메서드를 통해 cart 가 새롭게 생성되고

생성된 cart 에 cartItem 이 정상적으로 담긴것을 확인할 수 있다

 

 

1차적으로 장바구니에 '상품담기' 는 성공했다.

 

 


 

 

그럼 이제 장바구니에서 보완해야할 것은

 

1. 해당 유저의 장바구니(Cart) 가 없을때만 새로 생성한다 (+CartItemList 객체도 새롭게 생성)

2. 장바구니가 존재할 경우 Cart 의 CartItemList 에 상품을 추가(add) 한다

3. 이미 장바구니에 들어있는 상품은 추가하지 못한다

4. 품절된 상품은 장바구니에 추가하지 못한다

 

(장바구니 보관기간 설정 / 장바구니 목록 삭제, 조회 등은 오늘의 목표 해결과제가 아니니 일단 보류해두겠다)

 

 

 

1, 2 번을 반영해서 Service를 수정함.

public class CartService {
    private final CartRepository cartRepository;
    private final CartItemRepository cartItemRepository;
    private final UserRepository userRepository;
    private final ItemRepository itemRepository;

    //회원번호와 아이템번호 넣으면 장바구니에 상품을 추가해주는 메서드
    @Transactional
    public Cart save(Long userId, Long itemId) {
        log.info("cartService");
        User user = userRepository.findById(userId).orElseThrow(EntityNotFoundException::new);
        Item item = itemRepository.findItemById(itemId);

        Cart cart = cartRepository.findByUser(user.getName());
        log.info("cartId={}",cart.getId());

        if(cart.getId()==null){ // 장바구니가 존재하지 않는다면
            CartDto createCartDto = CartDto.builder()
                    .user(user)
                    .build();
            cart = cartRepository.save(createCartDto.toEntity());
        }

        CartItemDto cartItemDto = CartItemDto.builder()
                .cart(cart)
                .item(item)
                .build();

        //장바구니에 아이템이 존재하면 추가하지않는 메서드 추가
        CartItem cartItem = cartItemRepository.save(cartItemDto.toEntity());

        List<CartItem> cartItemList = cart.getCartItemList();
        if(cart.getCartItemList().isEmpty() || cart.getCartItemList()==null){ // 상품목록이 empty 라면
            cartItemList = new ArrayList<>();
        }

        cartItemList.add(cartItem);
        CartDto cartDto = CartDto.builder()
                .cartItemList(cartItemList)
                .user(cart.getUser())
                .id(cart.getId())
                .build(); //존재한다면 update 가 되야 함

        Cart cart1 = cartRepository.save(cartDto.toEntity());
        return cart;
    }
}

 

 

 

 

Cart 를 불러올 때 NullPointerException 이 또 떠서

1. 쿼리수정

2. 상품을 추가하는 시점에 장바구니 생성 X => 회원가입 하는 시점에 장바구니 생성

 

이렇게 두가지를 변경했다.

 

Cart 에 멤버변수로 있는 객체(User) 의 pk 를 매개변수로,

Cart 객체를 반환하는 쿼리를 작성해야했는데,

객체 지향적인 쿼리문 작성하는건 처음이라서 좀 헤멧다.

세번째 findCartByUser_Id 이걸 사용해야한다

 

 

 

근데 에러가 또 뜨네 ? ! 

 

 

    cartItemList.add(cartItem);
        CartDto cartDto = CartDto.builder()
                .cartItemList(cartItemList)
                .user(cart.getUser())
                .id(cart.getId())
                .build(); //존재한다면 update 가 되야 함
                
	 Cart cart1 = cartRepository.save(cartDto.toEntity());

 

이부분이 문제였는데

나는 cart 에 cartItemList 가 수정되었으니, cart를 업데이트 해야한다고 생각해서

업데이트 된 정보들을 DTO로 빌드하고 save 를 한번 더 했다 ( 첫번째 = 생성 / 두번째 = 업데이트 이 의도였음 )

 

근데 add 된 cartItemList 는 그냥 add 가 되고 끝인거다. 내가 수동으로 업데이트를 해줄 필요가 없는것이다.

 

수동으로 업데이트 한답시고 save 를 한번 더 사용해버리니, cart 가 중복으로 생겨버렸고, getCart() 를 했을 때 not unique 에러가 발생하며 Cart 로 반환되지 않는것이었다.

 

에러 해결만 3시간 정도 소요된것같은데, 가치가 있는 에러였다 ! 

 

 

 

최종

1,2,3 문제를 Service 에서 해결

4번은 view 에서 할 예정

  @Transactional
    public boolean save(Long userId, Long itemId) {
        log.info("cartService");
        User user = userRepository.findById(userId).orElseThrow(EntityNotFoundException::new);
        Item item = itemRepository.findItemById(itemId);

        Cart cart = cartRepository.findCartByUser_Id(userId);
        log.info("cartId={}", cart.getId());

        boolean isItemExist = cartItemRepository.existsCartItemByCart_IdAndItem_Id(cart.getId(), itemId);

        log.info("isItemExist={}",isItemExist);
        boolean addSuccess;

        if (!isItemExist) { //장바구니에 해당 상품이 없으면 추가
            CartItemDto cartItemDto = CartItemDto.builder()
                    .cart(cart)
                    .item(item)
                    .build();

            CartItem cartItem = cartItemRepository.save(cartItemDto.toEntity());

            List<CartItem> cartItemList = cartItemRepository.findByCart_Id(cart.getId());

            cartItemList.add(cartItem);

            addSuccess = true;
        } else { // 이미존재하면 false 반환
            addSuccess = false;
        }
        return addSuccess;
    }
public interface CartRepository extends JpaRepository<Cart,Long> {
    Cart findCartByUser_Id(Long userId);
}

 

 

 


※ 장바구니 상품 삭제하기

 

 

▶ 한개 삭제 (x 클릭)

 

Controller

    //장바구니 삭제 (한개)
    @GetMapping(value = "/cartRemove/{cartItemId}")
    public String cart_remove_get(@PathVariable("cartItemId")Long cartItemId, Model model){
        log.info("cartRemoveController cartItemId={}",cartItemId);

        cartService.cartRemove(cartItemId);

        String url = "/cart/cartlist", msg ="삭제되었습니다";
        model.addAttribute("url",url);
        model.addAttribute("msg",msg);
        return "common/message";
    }

 

Service

    //장바구니 개별 상품 삭제
    @Transactional
    public void cartRemove(Long cartItemId){
        cartItemRepository.deleteById(cartItemId);
    }

오홍홍 JPA delete 메서드 써보고싶었는데 여기서 씀

 

 

 

▶ 여러개 삭제 (checkbox 선택 => 삭제하기)

 

JS

List 에 Value(id) 넣어서 컨트롤러에 보내기

//삭제하기 버튼 클릭 시
    $(document).on('click', '#whiteDelBtn', function () {
        var delCheckedList=[];
        var result;
        $('input[name="delCheckbox"]:checked').each(function(){
            delCheckedList.push($(this).val());
        });
        if(delCheckedList.length<1){
            alert("선택된 상품이 없습니다");
        }else{
           result = confirm("선택한 상품을 삭제하시겠습니까 ?");
        }

        if(result){
            location.href = "/cart/cartRemoveList/"+delCheckedList;
        }else{

        }
    });

 

Controleller

List Size 만큼 For 문 돌려서

한개 삭제할때와 같은 메서드(cartRemove) 실행

   //장바구니 삭제 (여러개)
    @GetMapping(value ="/cartRemoveList/{cartItemIdList}")
    public String cart_remove_list_get(@PathVariable List<Long> cartItemIdList,Model model){
        log.info("cartRemoveList={}",cartItemIdList.size());
        for(Long cartItemId : cartItemIdList){
            cartService.cartRemove(cartItemId);
        }

        String url = "/cart/cartlist", msg ="삭제되었습니다";
        model.addAttribute("url",url);
        model.addAttribute("msg",msg);
        return"common/message";
    }