과제 : '장바구니' 버튼 클릭 시 해당 상품이 장바구니에 들어감
내가 만들 장바구니는
'세상에 단하나 존재하는 뜨개상품' 을 판매하는 마켓 특성상
장바구니에 담길 상품의 수량을 선택하지않아도 된다
모든 사용자는 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";
}
'프로젝트' 카테고리의 다른 글
[SpringBoot & JPA 프로젝트] 인터셉터 / 404,500 error page (0) | 2022.09.28 |
---|---|
[SpringBoot & JPA 프로젝트] 주문테이블 설계 (0) | 2022.09.05 |
[SpringBoot & JPA 프로젝트] 판매자 : 상품삭제하기 (delFlag) (0) | 2022.08.24 |
[Spring Boot & JPA 프로젝트] 상품등록 / 파일업로드(다중X) (0) | 2022.08.17 |
[SpringBoot & JPA 프로젝트] enum (0) | 2022.08.17 |