상품 상세 페이지에서 장바구니에 담을 수량 선택 후
장바구니 담기 버튼 클릭 시, 상품이 장바구니에 담기는 기능 구현해보자
Index
- 장바구니에 담을 상품 정보를 전달할 DTO 생성
- 장바구니 상품을 생성하는 메소드 추가
- 장바구니 찾는 쿼리문
- 장바구니 상품을 찾는 쿼리문
- 서비스에서 상품을 장바구니에 넣는 로직 작성
- 장바구니 컨트롤러 매핑
- 장바구니 담는 테스트 코드 작성
- JS에 장바구니 담기 버튼 기능 작성
상품 아이디
와 수량
을 전달 받을 DTO 생성@Getter @Setter
public class CartItemDto {
@NotNull(message = "상품 아이디는 필수 입력 값 입니다.")
private Long itemId;
@Min(value = 1, message = "최소 1개 이상 담아주세요")
private int count;
}
public static Cart createCart(Member member){
Cart cart = new Cart();
cart.setMember(member);
return cart;
}
// 장바구니에 담을 상품 생성
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 interface CartRepository extends JpaRepository<Cart, Long> {
// 로그인한 회원의 장바구니 조회
Cart findByMemberId(Long memberId);
}
장바구니 상품
조회public interface CartItemRepository extends JpaRepository<CartItem, Long> {
// 장바구니 들어갈 상품 저장하거나 조회
CartItem findByCartIdAndItemId(Long cartId, Long itemId);
}
장바구니 상품
을 생성해서 save()@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();
}
}
@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);
}
회원 정보
와 장바구니 상품에 저장된 회원 정보
비교수량
와 장바구니 상품에 저장된 수량
비교생략
생략
장바구니에 담긴 상품들을 조회하는 기능을 구현해보자
Index
- 장바구니 조회 페이지에 전달할 DTO 생성 (상세 정보 DTO)
- 장바구니 ID로 장바구니 상세 정보 DTO를 조회 하는 쿼리문 작성
- 장바구니에 들어있는 장바구니 상세 정보 리스트를 조회하는 로직
- 장바구니 페이지로 이동할 컨트롤러 매핑
- 상품 수량을 회원 장바구니 상품의 수량과 동기화
- 장바구니 상품의 수량을 업데이트
- 장바구니 상품 수량 업데이트 요청을 처리
@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;
}
}
JPQL 추가 공부..
// new 키워드와 해당 DTO패키지, 클래명을 적어줌 (파라미터는 DTO 클래스에 명시한 순으로 넣음)
@Query("select new com.example.demo.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(@Param("cartId") Long cartId);
// 로그인한 회원으로 장바구니에 들어있는 상품 조회
@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;
}
장바구니 리스트를 보여주는 페이지로 이동
CartDetailDto : 장바구니 조회 페이지에 보낼 DTO
@GetMapping(value = "/cart")
public String orderHist(Principal principal, Model model){
List<CartDetailDto> cartDetailList = cartService.getCartList(principal.getName());
model.addAttribute("cartItems", cartDetailList);
return "cart/cartList";
}
$(document).ready(function(){
// 1. 주문할 상품을 체크하거나 해제할 경우 총 주문 금액을 구하는 함수 호출
// .change로 변경 감지 시, function 실행
$("input[name=cartChkBox]").change( function(){
getOrderTotalPrice();
});
});
// 2. 총 주문 금액 구하는 함수
function getOrderTotalPrice(){
var orderTotalPrice = 0;
// 3. 현재 체크된 장바구니 상품들의 가격과 수량을 곱해서 총 주문 금액 계산
$("input[name=cartChkBox]:checked").each(function() {
var cartItemId = $(this).val();
var price = $("#price_" + cartItemId).attr("data-price");
var count = $("#count_" + cartItemId).val();
orderTotalPrice += price*count;
});
$("#orderTotalPrice").html(orderTotalPrice+'원');
}
// 4. 상품 수량 변경 시, 상품 가격과 상품 수량을 곱해서 상품 금액을 변경
function changeCount(obj){
var count = obj.value;
var cartItemId = obj.id.split('_')[1];
var price = $("#price_" + cartItemId).data("price");
var totalPrice = count*price;
$("#totalPrice_" + cartItemId).html(totalPrice+"원");
getOrderTotalPrice(); // 주문된 총 주문 금액 구함
updateCartItemCount(cartItemId, count);
}
// 5. 장바구니의 전체 상품을 체크하거나 체크 해제
function checkAll(){
if($("#checkall").prop("checked")){
$("input[name=cartChkBox]").prop("checked",true);
}else{
$("input[name=cartChkBox]").prop("checked",false);
}
// 변경된 주문 총 금액 계산
getOrderTotalPrice();
}
장바구니 상품의 수량을 업데이트하는 메서드 추가
public void updateCount(int count){
this.count = count;
}
// 장바구니 상품을 저장한 회원과 현재 로그인한 회원이 같은지 확인하는 메서드
@Transactional(readOnly = true)
public boolean validateCartItem(Long cartItemId, String email){
Member curMemebr = memberRepository.findByEmail(email);
CartItem cartItem = cartItemRepository.findById(cartItemId)
.orElseThrow(EntityNotFoundException::new);
Member savedMember = cartItem.getCart().getMember();
if(!StringUtils.equals(curMemebr.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);
}
PATCH
사용 // 요청된 자원의 일부를 업데이트할 때 Patch 사용 (장바구니 상품의 수량만 업데이트 함)
@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);
}
장바구니 페이지에서 수량 변경 시, 업데이트하도록 요청하는 JS 추가
PATCH
로 변경! function updateCartItemCount(cartItemId, count){
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
var url = "/cartItem/" + cartItemId+"?count=" + count;
$.ajax({
url : url,
type : "PATCH", // 부분 업데이트기 때문에 PATCH 타입으로 변경!!
beforeSend : function(xhr){
/* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
xhr.setRequestHeader(header, token);
},
dataType : "json",
cache : false,
success : function(result, status){
console.log("cartItem count update success");
},
error : function(jqXHR, status, error){
if(jqXHR.status == '401'){
alert('로그인 후 이용해주세요');
location.href='/members/login';
} else{
alert(jqXHR.responseJSON.message);
}
}
});
}
// 장바구니 상품 아이디로 장바구니 상품 제거
public void deleteCartItem(Long cartItemId) {
CartItem cartItem = cartItemRepository.findById(cartItemId)
.orElseThrow(EntityNotFoundException::new);
cartItemRepository.delete(cartItem);
}
Delete
사용 @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);
}
DELETE
로 변경 function deleteCartItem(obj){
var cartItemId = obj.dataset.id;
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
var url = "/cartItem/" + cartItemId;
$.ajax({
url : url,
type : "DELETE", // 형식을 DELETE로 변경!
beforeSend : function(xhr){
/* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
xhr.setRequestHeader(header, token);
},
dataType : "json",
cache : false,
success : function(result, status){
location.href='/cart';
},
error : function(jqXHR, status, error){
if(jqXHR.status == '401'){
alert('로그인 후 이용해주세요');
location.href='/members/login';
} else{
alert(jqXHR.responseJSON.message);
}
}
});
}
체크박스가 선택된 상품을 주문하는 로직을 추가해보자
장바구니는 기존 주문하기와 달리 여러 개의 상품을 하나의 주문에 담을 수 있다
또한, 주문한 상품을 장바구니에서 삭제 하는 로직도 추가해야한다.
// 장바구니 페이지에서 주문할 상품 데이터를 전달
@Getter
@Setter
public class CartOrderDto {
private Long cartItemId;
// 장바구니에서 여러 개의 상품을 주문하기 때문에 자기를 List로 가짐
private List<CartOrderDto> cartOrderDtoList;
}
/*
장바구니에서 주문할 상품 데이터를 전달받아 주문을 생성하는 로직
*/
public Long orders(List<OrderDto> orderDtoList, String email){
Member member = memberRepository.findByEmail(email);
List<OrderItem> orderItemList = new ArrayList<>(); // 1. 주문상품 리스트
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();
}
// 장바구니에서 주문하기 위해 보낸 리스트들을 주문 폼 리스트로 만들어서 주문로직에 전달
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;
}
@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);
}
// 장바구니에서 선택한 상품 주문을 처리
function orders(){
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
var url = "/cart/orders";
var dataList = new Array();
var paramData = new Object();
// 장바구니에서 체크된 장바구니 상품의 아이디 전달 위해
// 리스트에 장바구니 상품 아이디를 객체로 만들어 저장
$("input[name=cartChkBox]:checked").each(function() {
var cartItemId = $(this).val();
var data = new Object();
data["cartItemId"] = cartItemId;
dataList.push(data);
});
// 장바구니 상품 아이디를 저장한 리스트를 다시 저장
paramData['cartOrderDtoList'] = dataList;
var param = JSON.stringify(paramData);
$.ajax({
url : url,
type : "POST",
contentType : "application/json",
data : param,
beforeSend : function(xhr){
/* 데이터를 전송하기 전에 헤더에 csrf값을 설정 */
xhr.setRequestHeader(header, token);
},
dataType : "json",
cache : false,
success : function(result, status){
alert("주문이 완료 되었습니다.");
location.href='/orders';
},
error : function(jqXHR, status, error){
if(jqXHR.status == '401'){
alert('로그인 후 이용해주세요');
location.href='/members/login';
} else{
alert(jqXHR.responseJSON.message);
}
}
});