Spring boot 중고거래 쇼핑몰 사이트 만들기 프로젝트 7-3 (JPA로 장바구니 만들기)

전승재·2023년 8월 17일

Entity


장바구니 부분을 구현하는데 장바구니 아이템과 장바구니를 따로 나누었다.

@OneToMany(mappedBy = "basket")
private List<BasketItem> basketItems = new ArrayList<>();

@OneToMany, mappedBy: 단방향의 1:N 관계를 표시한다. basketItems는 해당 장바구니에 연결된 BasketItem 엔티티들을 나타낸다. mappedBy 속성은 관계의 주인을 지정하는데, 여기서는 BasketItem 엔티티의 basket 필드가 관계의 주인이다.

Basket

package com.shopingmall.seungjae.domain;

import jakarta.persistence.*;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Entity @Data
public class Basket {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long basketId;
    @OneToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "memberId")
    private Member member; //사용자 id와 1대1
    @OneToMany(mappedBy = "basket")//1대다 관계 여러개의 basketItem 가짐
    private List<BasketItem> basketItems = new ArrayList<>();
    public static Basket createBasket(Member member) { // 장바구니 생성 장바구니를 생성하는 정적 메서드.
    //Member를 인자로 받아 새로운 Basket 엔티티를 생성하고 member를 설정하여 반환.
        Basket basket = new Basket();
        basket.setMember(member);
        return basket;
    }
    @Override
    public String toString() {
        return "Basket{" +
                "basketId=" + basketId +
                '}';
    }
}

BasketItem

package com.shopingmall.seungjae.domain;

import jakarta.persistence.*;
import lombok.Data;

@Data @Entity
public class BasketItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long basketItemId; // 고유 id

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="basketId")
    private Basket basket; // 장바구니 고유 id

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="itemId")
    private Item item; // item id

    public static BasketItem createBasketItem(Basket basket, Item item) {
        BasketItem basketItem = new BasketItem();
        basketItem.setBasket(basket);
        basketItem.setItem(item);
        return basketItem;
    }
    @Override
    public String toString() {
        return "BasketItem{" +
                "basketItemId=" + basketItemId +
                // 필요한 필드만 출력
                '}';
    }
}

Service

BasketService

@Transactional DB에 저장하기 때문에 필수이다.
장바구니에 아이템을 추가하는 메서드 addBasket와 장바구니에서 아이템을 삭제하는 메서드 removeBasket이 있다.

  • addBasket은 member의 id를 통해 basket을 찾고 newItem을 createBasketItem을 통해 basketItem으로 만들고 이를 basketItemRepository에 저장한다.
    이렇게 되면 basketItem은 basket의 id와 newItem의 id를 저장하고 있기 때문에 member의 id에 따라 newItem 리스트를 불러올 수 있다.

  • removeBasket은 member를 통해 basket을 찾고 basket에서 deleteItem과 같은 basketItem을 찾아 이것이 null이 아니라면 삭제한다.

package com.shopingmall.seungjae.service;

import com.shopingmall.seungjae.domain.Basket;
import com.shopingmall.seungjae.domain.BasketItem;
import com.shopingmall.seungjae.domain.Item;
import com.shopingmall.seungjae.domain.Member;
import com.shopingmall.seungjae.repository.Basket.BasketItemRepository;
import com.shopingmall.seungjae.repository.Basket.BasketRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service @Transactional
@RequiredArgsConstructor
public class BasketService {
    private final BasketRepository basketRepository;
    private final BasketItemRepository basketItemRepository;
    public void addBasket(Member member, Item newItem) {
        Basket basket = basketRepository.findByMemberId(member.getId()); //넣을 장바구니
        BasketItem basketItem = BasketItem.createBasketItem(basket, newItem);
        //TODO 장바구니에 이미 있는 상품이면 추가 불가.
        basketItemRepository.save(basketItem);

    }
    public void removeBasket(Member member, Item deleteItem) {
        Basket basket = basketRepository.findByMemberId(member.getId());

        // 해당 상품을 장바구니에서 찾아서 삭제
        BasketItem basketItem = basket.getBasketItems()
                .stream()
                .filter(item -> item.getItem().equals(deleteItem))
                .findFirst()
                .orElse(null);

        if (basketItem != null) {
            basketItemRepository.delete(basketItem);
        }
    }
}

Controller

ShoppingMallBasketController

총 가격을 지역변수를 선언하고 for문을 통해서 구해준다.
총 개수는 size메서드를 통해 구한다.

package com.shopingmall.seungjae.controller.Basket;

import com.shopingmall.seungjae.controller.Member.SessionConst;
import com.shopingmall.seungjae.domain.Basket;
import com.shopingmall.seungjae.domain.BasketItem;
import com.shopingmall.seungjae.domain.Item;
import com.shopingmall.seungjae.domain.Member;
import com.shopingmall.seungjae.repository.Basket.BasketRepository;
import com.shopingmall.seungjae.repository.Item.ItemRepository;
import com.shopingmall.seungjae.service.BasketService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Controller
@RequestMapping("/basket")
@RequiredArgsConstructor
public class ShoppingMallBasketController {
    private final BasketService basketService;
    private final ItemRepository itemRepository;
    private final BasketRepository basketRepository;

    // 내 장바구니 페이지 return
    @GetMapping()
    public String BasketPage(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model) {
        Basket basket = basketRepository.findByMemberId(loginMember.getId());
        List<BasketItem> basketItems = basket.getBasketItems();
        model.addAttribute("basketItems", basketItems);
        model.addAttribute("totalAmount", basketItems.size());
        int totalPrice = 0; //총 가격 계산
        for (BasketItem basketItem : basketItems) {
            totalPrice += basketItem.getItem().getPrice();
        }
        model.addAttribute("totalPrice",totalPrice);
        return "basket/basketPage";
    }
//    물품 추가하기
    @PostMapping("/{itemId}")
    public String AddBasket(@PathVariable Long itemId, @SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember) {
        Item item = itemRepository.findById(itemId);
        basketService.addBasket(loginMember, item); //Service 참고
        return "redirect:/basket";
    }
    //물품 삭제하기
    @GetMapping("/{itemId}")
    public String deleteItemAtBasket(@PathVariable Long itemId, @SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember) {
        Item item = itemRepository.findById(itemId);
        basketService.removeBasket(loginMember, item); //Service 참고
        return "redirect:/basket";
    }
}

Thymeleaf

BasketPage

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>장바구니</title>
</head>
<body>
<h1>장바구니</h1>

<table>
  <thead>
  <tr>
    <th>상품명</th>
    <th>가격</th>
  </tr>
  </thead>
  <tbody>
  <tr th:each="items : ${basketItems}">
    <td th:text="${items.item.itemName}"></td>
    <td th:text="${items.item.price}"></td>
    <td><button type="button" th:onclick="|location.href = '@{/basket/{itemId}(itemId=${items.item.itemId})}'|">
      장바구니에서 상품 삭제하기
    </button></td>
  </tr>
  </tbody>
</table>

<h3>총 합계: <span th:text="${totalPrice}"></span></h3>
<h3>총 수량: <span th:text="${totalAmount}"></span></h3>
<a href="/checkout">결제하기</a>
<div class="form-group">
  <button type="button" onclick="location.href='/';">홈으로 돌아가기</button>
</div>
</body>
</html>

결과 사진

Basket은 memebr_id와 같다.
MemberService에서 join 메서드 내에 아래의 코드를 넣어 사용자가 회원가입할 때 장바구니 역시 자동 생성되도록 해주었다.

 Basket basket = Basket.createBasket(member);
 basketRepository.save(basket);

제품을 장바구니에 추가하면 아래와 같이 추가 된다. 장바구니에서 상품 삭제하기 를 누르면 상품이 삭제된다.
총 합계는 지역변수를 통해서 for문으로 상품의 가격을 다 더해주었다.
총 수량은 list의 size메서드를 통해 구해서 model.addAttribute 해주었다.
아래는 basket_item 테이블이다. item_id를 외래키로 가지고 있고, basket_id를 가지고 있어 사용자가 누군지를 이 id를 통해 확인한다.

다음 할일

다음번엔 우리 프로젝트의 ERD를 다시 한번 고민해보고, 작성한다. 프로젝트를 어디까지 진행할지를 정해야 할 것 같다.
SQLD자격증 시험이 9월 9일이라 8월 25일까지는 이 중고거래 쇼핑몰 프로젝트를 어느정도 마무리하려고 한다

0개의 댓글