ACHO.pk devlog

[Springboot] 장바구니 기능 본문

프레임워크/Springboot

[Springboot] 장바구니 기능

Acho 2023. 3. 27. 23:52

1. 장바구니 담기

상품 상세 페이지에서 장바구니에 담을 상품의 아이디와 수량을 전달 받을 클래스를 생성한다. 

장바구니에 담을 상품의 최소 수량은 1개 이상으로 제한한다.

 

dto > CartItemDto

package com.shop.shop.dto;

import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;

@Getter @Setter
public class CartItemDto {

    @NotNull(message = "상품 아이디는 필수 입력 값 입니다.")
    private Long itemId;

    @Min(value = 1, message = "최소 1개 이상 담아주세요")
    private int count;

}

 

회원 한 명당 1개의 장바구니를 갖으므로 처음 장바구니에 상품을 담을 때는 해당 회원의 장바구니를 생성해줘야한다.

Cart 클래스에 회원 엔티티를 파라미터로 받아서 장바구니 엔티티를 생성하는 로직을 추가한다.

entity > Cart

package com.shop.shop.entity;


import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.*;

@Entity
@Table(name = "cart")
@Getter
@Setter
@ToString
public class Cart extends BaseEntity{
    ...생략...

    public static Cart createCart(Member member){
        Cart cart = new Cart();
        cart.setMember(member);
        return cart;
    }
}

 

장바구니에 담을 상품 엔티티를 생성하는 메소드와 장바구니에 담을 수량을 증가시켜 주는 메소드를 CartItem클래스에 추가한다.

entity > CartItem

package com.shop.shop.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Getter
@Setter
@Table(name="cart_item")
public class CartItem extends BaseEntity{
	...생략...

    public static CartItem createCartItem(Cart cart, Item item, int count) {
        CartItem cartItem = new CartItem();
        cartItem.setCart(cart);
        cartItem.setItem(item);
        cartItem.setCount(count);
        return cartItem;
    }

    public void addCount(int count){
        this.count += count;
    }
}

▹장바구니에 기존에 담겨 있는 상품인데, 해당 상품을 추가로 장바구니에 담을 때 기존 수량에 현재 담을 수량을 더 해줄 때 사용할 메소드이다. 

public void addCount(int count){

 

현재 로그인한 회원의 Cart 엔티티를 찾기 위해서 CartRepository에 쿼리 메소드를 추가한다.

repository > CartRepository

package com.shop.shop.repository;

import com.shop.shop.entity.Cart;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CartRepository extends JpaRepository<Cart, Long> {
    Cart findByMemberId(Long memberId);
}

 

장바구니에 들어갈 상품을 저장하거나 조회하기 위해서 CartItemRepository 인터페이스를 생성한다.

repository > CartItemRepository 

package com.shop.shop.repository;

import com.shop.shop.entity.CartItem;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CartItemRepository extends JpaRepository<CartItem, Long> {

    CartItem findByCartIdAndItemId(Long cartId, Long itemId);
}

▹카트 아이디와 상품 아이디를 이용해서 상품이 장바구니에 들어있는지 조회한다.

CartItem findByCartIdAndItemId(Long cartId, Long itemId);

 

장바구니에 상품을 담는 로직을 작성해보자.

service > CartService

package com.shop.shop.service;

import com.shop.shop.dto.CartItemDto;
import com.shop.shop.entity.Cart;
import com.shop.shop.entity.CartItem;
import com.shop.shop.entity.Item;
import com.shop.shop.entity.Member;
import com.shop.shop.repository.CartItemRepository;
import com.shop.shop.repository.CartRepository;
import com.shop.shop.repository.ItemRepository;
import com.shop.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;

import org.thymeleaf.util.StringUtils;


@Service
@RequiredArgsConstructor
@Transactional
public class CartService {

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

    public Long addCart(CartItemDto cartItemDto, String email){

        Item item = itemRepository.findById(cartItemDto.getItemId())
                .orElseThrow(EntityNotFoundException::new);
        Member member = memberRepository.findByEmail(email);

        Cart cart = cartRepository.findByMemberId(member.getId());
        if(cart == null){
            cart = Cart.createCart(member);
            cartRepository.save(cart);
        }

        CartItem savedCartItem = cartItemRepository.findByCartIdAndItemId(cart.getId(), item.getId());

        if(savedCartItem != null){
            savedCartItem.addCount(cartItemDto.getCount());
            return savedCartItem.getId();
        } else {
            CartItem cartItem = CartItem.createCartItem(cart, item, cartItemDto.getCount());
            cartItemRepository.save(cartItem);
            return cartItem.getId();
        }
    }
}

▹장바구니에 담을 상품 엔티티를 조회한다.

Item item = itemRepository.findById(cartItemDto.getItemId())

 

▹현재 로그인한 회원 엔티티를 조회한다.

Member member = memberRepository.findByEmail(email);

 

▹현재 로그인한 회원의 장바구니 엔티티를 조회한다.

Cart cart = cartRepository.findByMemberId(member.getId());

 

▹상품을 처음으로 장바구니에 담을 경우 해당 회원의 장바구니 엔티티를 생성한다.

if(cart == null){

 

▹현재 상품이 장바구니에 이미 들어가 있는지 조회한다.

CartItem savedCartItem = cartItemRepository.findByCartIdAndItemId(cart.getId(), item.getId());

 

▹장바구니에 이미 있던 상품일 경우 기존 수량에 현재 장바구니에 담을 수량 만큼을 더해준다.

savedCartItem.addCount(cartItemDto.getCount());

 

▹장바구니 엔티티, 상품 엔티티, 장바구니에 담을 수량을 이용해서 CartItem 엔티티를 생성한다.

CartItem cartItem = CartItem.createCartItem(cart, item, cartItemDto.getCount());

 

▹장바구니에 들어갈 상품을 저장한다.

cartItemRepository.save(cartItem);

 

 

장바구니와 관련된 요청을 처리하기 위해서 CartController 클래스를 생성한다.

controller > CartController

package com.shop.shop.controller;

import com.shop.shop.dto.CartItemDto;
import com.shop.shop.service.CartService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.validation.Valid;
import java.security.Principal;
import java.util.List;



@Controller
@RequiredArgsConstructor
public class CartController {

    private final CartService cartService;

    @PostMapping(value = "/cart")
    public @ResponseBody ResponseEntity order(@RequestBody @Valid CartItemDto cartItemDto, BindingResult bindingResult, Principal principal){

        if(bindingResult.hasErrors()){
            StringBuilder sb = new StringBuilder();
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();

            for (FieldError fieldError : fieldErrors) {
                sb.append(fieldError.getDefaultMessage());
            }

            return new ResponseEntity<String>(sb.toString(), HttpStatus.BAD_REQUEST);
        }

        String email = principal.getName();
        Long cartItemId;

        try {
            cartItemId = cartService.addCart(cartItemDto, email);
        } catch(Exception e){
            return new ResponseEntity<String>(e.getMessage(), HttpStatus.BAD_REQUEST);
        }

        return new ResponseEntity<Long>(cartItemId, HttpStatus.OK);
    }
}

▹장바구니에 담을 상품 정보를 받는 cartItemDto 객체에 데이터 바인딩 시 에러가 있는지 검사한다.

if(bindingResult.hasErrors()){

 

▹현재 로그인한 회원의 이메일 정보를 변수에 저장한다.

String email = principal.getName();

 

▹화면으로부터 넘어온 장바구니에 담을 상품 정보와 현재 로그인한 회원의 이메일 정보를 이용하여 장바구니에 상품을 담는 로직을 호출한다.

cartItemId = cartService.addCart(cartItemDto, email);

 

▹결과값으로 생성된 장바구니 상품 아이디와 요청이 성공하였다는 HTTP 응답 상태 코드를 반환한다.

return new ResponseEntity<Long>(cartItemId, HttpStatus.OK);

 

1-1. 테스트 코드

상품을 장바구니에 담는 로직을 테스트해보자

service > CartServiceTest

package com.shop.shop.service;

import com.shop.shop.constant.ItemSellStatus;
import com.shop.shop.dto.CartItemDto;
import com.shop.shop.entity.CartItem;
import com.shop.shop.entity.Item;
import com.shop.shop.entity.Member;
import com.shop.shop.repository.CartItemRepository;
import com.shop.shop.repository.ItemRepository;
import com.shop.shop.repository.MemberRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityNotFoundException;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
@Transactional
@TestPropertySource(locations="classpath:application-test.properties")
class CartServiceTest {

    @Autowired
    ItemRepository itemRepository;

    @Autowired
    MemberRepository memberRepository;

    @Autowired
    CartService cartService;

    @Autowired
    CartItemRepository cartItemRepository;

    public Item saveItem(){
        Item item = new Item();
        item.setItemNm("테스트 상품");
        item.setPrice(10000);
        item.setItemDetail("테스트 상품 상세 설명");
        item.setItemSellStatus(ItemSellStatus.SELL);
        item.setStockNumber(100);
        return itemRepository.save(item);
    }

    public Member saveMember(){
        Member member = new Member();
        member.setEmail("test@test.com");
        return memberRepository.save(member);
    }

    @Test
    @DisplayName("장바구니 담기 테스트")
    public void addCart(){
        Item item = saveItem();
        Member member = saveMember();

        CartItemDto cartItemDto = new CartItemDto();
        cartItemDto.setCount(5);
        cartItemDto.setItemId(item.getId());

        Long cartItemId = cartService.addCart(cartItemDto, member.getEmail());
        CartItem cartItem = cartItemRepository.findById(cartItemId)
                .orElseThrow(EntityNotFoundException::new);

        assertEquals(item.getId(), cartItem.getItem().getId());
        assertEquals(cartItemDto.getCount(), cartItem.getCount());
    }

}

▹테스트를 위해서 장바구니에 담을 상품과 회원 정보를 저장하는 메소드를 생성한다.

public Item saveItem(){
public Member saveMember(){

 

▹장바구니에 담을 상품과 수량을 cartItemDto 객체에 세팅한다.

cartItemDto.setCount(5);
cartItemDto.setItemId(item.getId());

 

▹상품을 장바구니에 담는 로직 호출 결과 생성된 장바구니 상품 아이디를 cartItemId 변수에 저장한다.

Long cartItemId = cartService.addCart(cartItemDto, member.getEmail());

 

▹장바구니 상품 아이디를 이용하여 생성된 장바구니 상품 정보를 조회한다.

CartItem cartItem = cartItemRepository.findById(cartItemId)

 

▹상품 아이디와 장바구니에 저장된 상품 아이디가 같다면 테스트가 통과한다.

assertEquals(item.getId(), cartItem.getItem().getId());

 

▹장바구니에 담았던 수량과 실제로 장바구니에 저장된 수량이 같다면 테스트가 통과한다.

assertEquals(cartItemDto.getCount(), cartItem.getCount());

테스트 실행 결과 장바구니에 상품을 담는 로직이 정상적으로 동작함을 볼 수 있다.

 

 

상품 상세 페이지에서 구현한 장바구니 담기 로직을 호출하는 코드를 스크립트 영역에 추가해보자.

아래 링크에서 코드를 가져오자.

https://github.com/roadbook2/shop/blob/master/src/main/resources/templates/item/itemDtl.html

<장바구니 담기> 버튼을 클릭하면 상품을 장바구니에 담았다는 메시지가 나타나고, <확인>을 누르면 메인 페이지로 이동한다.

 

 

2. 장바구니 조회하기

먼저 장바구니 조회 페이지에 전달할 DTO 클래스를 생성하자. JPQL로 쿼리 작성시 생성자를 이용해서 DTO로 바로 반환하는 방법을 알아보자.

dto > CartDetailDto

package com.shop.shop.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class CartDetailDto {

    private Long cartItemId; //장바구니 상품 아이디

    private String itemNm; //상품명

    private int price; //상품 금액

    private int count; //수량

    private String imgUrl; //상품 이미지 경로

    public CartDetailDto(Long cartItemId, String itemNm, int price, int count, String imgUrl){
        this.cartItemId = cartItemId;
        this.itemNm = itemNm;
        this.price = price;
        this.count = count;
        this.imgUrl = imgUrl;
    }

}

▹장바구니 페이지에 전달한 데이터를 생성자의 파라미터로 만들어준다.

public CartDetailDto(Long cartItemId, String itemNm, int price, int count, String imgUrl){

 

장바구니 페이지에 전달할 CartDetailDto 리스트를 쿼리 하나로 조회하는 JPQL 문을 작성한다.

연관 관계 매핑을 지연 로딩으로 설정할 경우 엔티티에 매핑된 다른 엔티티를 조회할 때 추가적으로 쿼리문이 실행된다.

repository > CartItemRepository

package com.shop.shop.repository;

import com.shop.shop.dto.CartDetailDto;
import com.shop.shop.entity.CartItem;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface CartItemRepository extends JpaRepository<CartItem, Long> {

    CartItem findByCartIdAndItemId(Long cartId, Long itemId);
    @Query("select new com.shop.shop.dto.CartDetailDto(ci.id, i.itemNm, i.price, ci.count, im.imgUrl) " +
            "from CartItem ci, ItemImg im " +
            "join ci.item i " +
            "where ci.cart.id = :cartId " +
            "and im.item.id = ci.item.id " +
            "and im.repimgYn = 'Y' " +
            "order by ci.regTime desc"
    )
    List<CartDetailDto> findCartDetailDtoList(Long cartId);
}

▹CartDetailDto의 생성자를 이용하여 DTO를 반환할 때는

new com.shop.shop.dto.CartDetailDto(ci.id, i.itemNm, i.price, ci.count, im.imgUrl) "

처럼 new 키워드와 해당 DTO의 패키지, 클래스명을 적어준다. 또한 생성자의 파라미터 순서는 DTO 클래스에 명시한 순으로 넣어줘야 한다.

@Query("select new com.shop.shop.dto.CartDetailDto(ci.id, i.itemNm, i.price, ci.count, im.imgUrl) " +

 

▹장바구니에 담겨있는 상품의 대표 이미지만 가지고 오도록 조건문을 작성한다.

"and im.item.id = ci.item.id " +
"and im.repimgYn = 'Y' " +

 

 

현재 로그인한 회원의 정보를 이용하여 장바구니에 들어있는 상품을 조회하는 로직을 작성해보자.

service > CartService

package com.shop.shop.service;

import com.shop.shop.dto.CartDetailDto;
import com.shop.shop.dto.CartItemDto;
import com.shop.shop.entity.Cart;
import com.shop.shop.entity.CartItem;
import com.shop.shop.entity.Item;
import com.shop.shop.entity.Member;
import com.shop.shop.repository.CartItemRepository;
import com.shop.shop.repository.CartRepository;
import com.shop.shop.repository.ItemRepository;
import com.shop.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;

import org.thymeleaf.util.StringUtils;


@Service
@RequiredArgsConstructor
@Transactional
public class CartService {
	...생략...

    @Transactional(readOnly = true)
    public List<CartDetailDto> getCartList(String email){

        List<CartDetailDto> cartDetailDtoList = new ArrayList<>();

        Member member = memberRepository.findByEmail(email);
        Cart cart = cartRepository.findByMemberId(member.getId());
        if(cart == null){
            return cartDetailDtoList;
        }

        cartDetailDtoList = cartItemRepository.findCartDetailDtoList(cart.getId());
        return cartDetailDtoList;
    }
}

▹현재 로그인한 회원의 장바구니 엔티티를 조회한다.

Cart cart = cartRepository.findByMemberId(member.getId());

 

▹장바구니에 상품을 한 번도 안 담았을 경우 장바구니 엔티티가 없으므로 빈 리스트를 반환한다.

if(cart == null){

 

▹장바구니에 담겨 있는 상품 정보를 조회한다.

cartDetailDtoList = cartItemRepository.findCartDetailDtoList(cart.getId());

 

 

장바구니 페이지로 이동할 수 있도록 컨트롤러에 추가하자.

controller > CartController

package com.shop.shop.controller;

import com.shop.shop.dto.CartDetailDto;
import com.shop.shop.dto.CartItemDto;
import com.shop.shop.service.CartService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.validation.Valid;
import java.security.Principal;
import java.util.List;



@Controller
@RequiredArgsConstructor
public class CartController {

    ...생략....
    
    @GetMapping(value = "/cart")
    public String orderHist(Principal principal, Model model){
        List<CartDetailDto> cartDetailList = cartService.getCartList(principal.getName());
        model.addAttribute("cartItems", cartDetailList);
        return "cart/cartList";
    }
}

▹현재 로그인한 사용자의 이메일 정보를 이용하여 장바구니에 담겨있는 상품 정보를 조회한다.

List<CartDetailDto> cartDetailList = cartService.getCartList(principal.getName());

 

▹조회한 장바구니 상품 정보를 뷰로 전달한다.

model.addAttribute("cartItems", cartDetailList);

 

 

조회한 장바구니 상품 정보를 이용하여 장바구니 목록을 보여주는 페이지를 구현해보자. 

아래 링크에서 코드를 가져오면 된다.

https://github.com/roadbook2/shop/blob/master/src/main/resources/templates/cart/cartList.html

 

 

장바구니에서 상품의 수량을 변경할 경우 실시간으로 해당 회원의 장바구니 상품의 수량도 변경하도록 로직을 추가한다.

entity > CartItem

package com.shop.shop.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Getter
@Setter
@Table(name="cart_item")
public class CartItem extends BaseEntity{

    ...생략...

    public void updateCount(int count){
        this.count = count;
    }
}

 

CartService 클래스에 장바구니 상품의 수량을 업데이트하는 로직을 추가한다. 현재 로그인한 회원과 해당 장바구니 상품을 저장한 회원이 같은지 검사하는 로직도 작성한다.

service > CartService

package com.shop.shop.service;

import com.shop.shop.dto.CartDetailDto;
import com.shop.shop.dto.CartItemDto;
import com.shop.shop.entity.Cart;
import com.shop.shop.entity.CartItem;
import com.shop.shop.entity.Item;
import com.shop.shop.entity.Member;
import com.shop.shop.repository.CartItemRepository;
import com.shop.shop.repository.CartRepository;
import com.shop.shop.repository.ItemRepository;
import com.shop.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;

import org.thymeleaf.util.StringUtils;


@Service
@RequiredArgsConstructor
@Transactional
public class CartService {
	...생략...
    
    @Transactional(readOnly = true)
    public boolean validateCartItem(Long cartItemId, String email){
        Member curMember = memberRepository.findByEmail(email);
        CartItem cartItem = cartItemRepository.findById(cartItemId)
                .orElseThrow(EntityNotFoundException::new);
        Member savedMember = cartItem.getCart().getMember();

        if(!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())){
            return false;
        }

        return true;
    }

    public void updateCartItemCount(Long cartItemId, int count){
        CartItem cartItem = cartItemRepository.findById(cartItemId)
                .orElseThrow(EntityNotFoundException::new);

        cartItem.updateCount(count);
    }
}

▹현재 로그인한 회원을 조회한다.

Member curMember = memberRepository.findByEmail(email);

 

▹장바구니 상품을 저장한 회원을 조회한다.

Member savedMember = cartItem.getCart().getMember();

 

▹현재 로그인한 회원과 장바구니 상품을 저장한 회원이 다를 경우 false, 같으면 true를 반환한다.

if(!StringUtils.equals(curMember.getEmail(), savedMember.getEmail())){
return true;

 

▹장바구니 상품의 수량을 업데이트하는 메소드이다.

public void updateCartItemCount(Long cartItemId, int count){

 

 

장바구니 상품의 수량을 업데이트하는 요청을 처리할 수 있도록 로직을 추가한다.

controller > CartController

package com.shop.shop.controller;

import com.shop.shop.dto.CartDetailDto;
import com.shop.shop.dto.CartItemDto;
import com.shop.shop.service.CartService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.security.Principal;
import java.util.List;



@Controller
@RequiredArgsConstructor
public class CartController {

    ...생략...

    @PatchMapping(value = "/cartItem/{cartItemId}")
    public @ResponseBody ResponseEntity updateCartItem(@PathVariable("cartItemId") Long cartItemId, int count, Principal principal){

        if(count <= 0){
            return new ResponseEntity<String>("최소 1개 이상 담아주세요", HttpStatus.BAD_REQUEST);
        } else if(!cartService.validateCartItem(cartItemId, principal.getName())){
            return new ResponseEntity<String>("수정 권한이 없습니다.", HttpStatus.FORBIDDEN);
        }

        cartService.updateCartItemCount(cartItemId, count);
        return new ResponseEntity<Long>(cartItemId, HttpStatus.OK);
    }
}

▹HTTP 메소드에서 PATCH는 요청된 자원의 일부를 업데이트할 때 PATCH를 사용한다. 장바구니 상품의 수량만 업데이트하기 때문에 @PatchMapping을 사용한다.

@PatchMapping(value = "/cartItem/{cartItemId}")

 

▹장바구니에 담겨있는 상품의 개수를 0개 이하로 업데이트 요청을 할 때 에러 메시지를 담아서 반환한다.

if(count <= 0){

 

▹수정 권한을 체크한다.

} else if(!cartService.validateCartItem(cartItemId, principal.getName())){

 

▹장바구니 상품의 개수를 업데이트한다.

cartService.updateCartItemCount(cartItemId, count);

 

 

상품 정보에 있는 x 버튼을 클릭할 때 장바구니에 넣어 놓은 상품을 삭제해보자.

장바구니 상품 번호를 파라미터로 받아서 삭제하는 로직을 추가한다.

servicer > CartService 

package com.shop.shop.service;

import com.shop.shop.dto.CartDetailDto;
import com.shop.shop.dto.CartItemDto;
import com.shop.shop.entity.Cart;
import com.shop.shop.entity.CartItem;
import com.shop.shop.entity.Item;
import com.shop.shop.entity.Member;
import com.shop.shop.repository.CartItemRepository;
import com.shop.shop.repository.CartRepository;
import com.shop.shop.repository.ItemRepository;
import com.shop.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;

import org.thymeleaf.util.StringUtils;


@Service
@RequiredArgsConstructor
@Transactional
public class CartService {

    ...생략...

    public void deleteCartItem(Long cartItemId) {
        CartItem cartItem = cartItemRepository.findById(cartItemId)
                .orElseThrow(EntityNotFoundException::new);
        cartItemRepository.delete(cartItem);
    }
}

 

장바구니 상품을 삭제하는 요청을 처리할 수 있도록 로직을 추가한다.

controller > CartController

package com.shop.shop.controller;

import com.shop.shop.dto.CartDetailDto;
import com.shop.shop.dto.CartItemDto;
import com.shop.shop.service.CartService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.security.Principal;
import java.util.List;



@Controller
@RequiredArgsConstructor
public class CartController {

    ...생략...

    @DeleteMapping(value = "/cartItem/{cartItemId}")
    public @ResponseBody ResponseEntity deleteCartItem(@PathVariable("cartItemId") Long cartItemId, Principal principal){

        if(!cartService.validateCartItem(cartItemId, principal.getName())){
            return new ResponseEntity<String>("수정 권한이 없습니다.", HttpStatus.FORBIDDEN);
        }

        cartService.deleteCartItem(cartItemId);

        return new ResponseEntity<Long>(cartItemId, HttpStatus.OK);
    }

}

▹HTTP 메소드에서 DELETE의 경우 요청된 자원을 삭제할 때 사용한다. 장바구니 상품을 삭제하기 때문에 @DeleteMapping을 사용한다.

@DeleteMapping(value = "/cartItem/{cartItemId}")

 

▹수정 권한을 체크한다.

if(!cartService.validateCartItem(cartItemId, principal.getName())){

 

▹해당 장바구니 상품을 삭제한다.

cartService.deleteCartItem(cartItemId);

 

 

3. 장바구니 상품 주문하기 

장바구니 페이지에서 주문할 상품 데이터를 전달할 DTO를 생성하자.

dto > CartOrderDto

package com.shop.shop.dto;

import lombok.Getter;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
public class CartOrderDto {

    private Long cartItemId;

    private List<CartOrderDto> cartOrderDtoList;

}

▹장바구니에서 여러 개의 상품을 주문하므로 CartOrderDto 클래스가 자기 자신을 List로 가지고 있도록 만들자

private List<CartOrderDto> cartOrderDtoList;

 

장바구니에서 주문할 상품 데이터를 전달받아서 주문을 생성하는 로직을 만들어보자

service > OrderService

package com.shop.shop.service;

import com.shop.shop.dto.OrderItemDto;
import com.shop.shop.entity.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.shop.shop.dto.OrderDto;
import com.shop.shop.dto.OrderHistDto;
import com.shop.shop.repository.ItemImgRepository;
import com.shop.shop.repository.ItemRepository;
import com.shop.shop.repository.MemberRepository;
import com.shop.shop.repository.OrderRepository;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.thymeleaf.util.StringUtils;

import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;

@Service
@Transactional
@RequiredArgsConstructor
public class OrderService {

   ...생략....
   
    public Long orders(List<OrderDto> orderDtoList, String email){

        Member member = memberRepository.findByEmail(email);
        List<OrderItem> orderItemList = new ArrayList<>();

        for (OrderDto orderDto : orderDtoList) {
            Item item = itemRepository.findById(orderDto.getItemId())
                    .orElseThrow(EntityNotFoundException::new);

            OrderItem orderItem = OrderItem.createOrderItem(item, orderDto.getCount());
            orderItemList.add(orderItem);
        }

        Order order = Order.createOrder(member, orderItemList);
        orderRepository.save(order);

        return order.getId();
    }

}

▹주문할 상품 리스트를 만들어 준다.

for (OrderDto orderDto : orderDtoList) {

 

▹현재 로그인한 회원과 주문 상품 목록을 이용하여 주문 엔티티를 만든다.

Order order = Order.createOrder(member, orderItemList);

 

▹주문 데이터를 저장한다.

orderRepository.save(order);

 

주문 로직으로 전달할 orderDto 리스트 생성 및 주문 로직 호출, 주문한 상품은 장바구니에서 제거하는 로직을 구현한다.

service > CartService

package com.shop.shop.service;

import com.shop.shop.dto.CartDetailDto;
import com.shop.shop.dto.CartItemDto;
import com.shop.shop.dto.CartOrderDto;
import com.shop.shop.dto.OrderDto;
import com.shop.shop.entity.Cart;
import com.shop.shop.entity.CartItem;
import com.shop.shop.entity.Item;
import com.shop.shop.entity.Member;
import com.shop.shop.repository.CartItemRepository;
import com.shop.shop.repository.CartRepository;
import com.shop.shop.repository.ItemRepository;
import com.shop.shop.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;

import org.thymeleaf.util.StringUtils;


@Service
@RequiredArgsConstructor
@Transactional
public class CartService {
	...생략...

    public Long orderCartItem(List<CartOrderDto> cartOrderDtoList, String email){
        List<OrderDto> orderDtoList = new ArrayList<>();

        for (CartOrderDto cartOrderDto : cartOrderDtoList) {
            CartItem cartItem = cartItemRepository
                    .findById(cartOrderDto.getCartItemId())
                    .orElseThrow(EntityNotFoundException::new);

            OrderDto orderDto = new OrderDto();
            orderDto.setItemId(cartItem.getItem().getId());
            orderDto.setCount(cartItem.getCount());
            orderDtoList.add(orderDto);
        }

        Long orderId = orderService.orders(orderDtoList, email);
        for (CartOrderDto cartOrderDto : cartOrderDtoList) {
            CartItem cartItem = cartItemRepository
                    .findById(cartOrderDto.getCartItemId())
                    .orElseThrow(EntityNotFoundException::new);
            cartItemRepository.delete(cartItem);
        }

        return orderId;
    }
}

▹장바구니 페이지에서 전달받은 주문 상품 번호를 이용하여 주문 로직으로 전달할 orderDto 객체를 만든다.

for (CartOrderDto cartOrderDto : cartOrderDtoList) {

 

▹장바구니에 담은 상품을 주문하도록 주문 로직을 호출한다.

Long orderId = orderService.orders(orderDtoList, email);

 

▹주문한 상품들을 장바구니에서 제거한다.

for (CartOrderDto cartOrderDto : cartOrderDtoList) {

 

장바구니 상품의 수량을 업데이트하는 요청을 처리할 수 있도록 로직을 추가한다.

controller > CartController

package com.shop.shop.controller;

import com.shop.shop.dto.CartDetailDto;
import com.shop.shop.dto.CartItemDto;
import com.shop.shop.dto.CartOrderDto;
import com.shop.shop.service.CartService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.security.Principal;
import java.util.List;



@Controller
@RequiredArgsConstructor
public class CartController {

   ...생략...

    @PostMapping(value = "/cart/orders")
    public @ResponseBody ResponseEntity orderCartItem(@RequestBody CartOrderDto cartOrderDto, Principal principal){

        List<CartOrderDto> cartOrderDtoList = cartOrderDto.getCartOrderDtoList();

        if(cartOrderDtoList == null || cartOrderDtoList.size() == 0){
            return new ResponseEntity<String>("주문할 상품을 선택해주세요", HttpStatus.FORBIDDEN);
        }

        for (CartOrderDto cartOrder : cartOrderDtoList) {
            if(!cartService.validateCartItem(cartOrder.getCartItemId(), principal.getName())){
                return new ResponseEntity<String>("주문 권한이 없습니다.", HttpStatus.FORBIDDEN);
            }
        }

        Long orderId = cartService.orderCartItem(cartOrderDtoList, principal.getName());
        return new ResponseEntity<Long>(orderId, HttpStatus.OK);
    }

}

▹주문할 상품을 선택하지 않았는지 체크한다.

if(cartOrderDtoList == null || cartOrderDtoList.size() == 0){

 

▹주문 권한을 체크한다.

for (CartOrderDto cartOrder : cartOrderDtoList) {

 

▹주문 로직 호출 결과 생성된 주문 번호를 반환받는다.

Long orderId = cartService.orderCartItem(cartOrderDtoList, principal.getName());

 

▹생성된 주문 번호와 요청이 성공했다는 HTTP 응답 상태 코드를 반환한다.

return new ResponseEntity<Long>(orderId, HttpStatus.OK);

이렇게 하면

요청이 성공했을 때 주문이 완료되었다는 메시지가 나타난다. <확인> 버튼을 클릭하면 구매 이력 페이지로 이동하며 장바구니에 담았던 상품들이 정상적으로 주문이 됐다는 것을 확인할 수 있다.

 

 

Comments