상품 상세 페이지에서 장바구니에 담을 수량을 선택하고 장바구니 담기 버튼을 클릭할 때 상품이 장바구니에 담기는 기능을 먼저 구현하겠습니다.
package me.jincrates.gobook.web.dto;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
@Getter
@NoArgsConstructor
public class CartItemDto {
@NotNull(message = "상품 아이디는 필수 입력 값입니다.")
private Long itemId;
@Min(value = 1, message = "최소 1개 이상 담아주세요.")
private int count;
@Builder
public CartItemDto(Long itemId, int count) {
this.itemId = itemId;
this.count = count;
}
}
회원 한 명당 1개의 장바구니를 갖으므로 Cart 클래스에 회원 엔티티를 파라미터로 받아서 장바구니 엔티티를 생성하는 로직을 추가합니다.
package me.jincrates.gobook.domain.carts;
///...
@NoArgsConstructor
@Table(name = "cart")
@Entity
public class Cart extends BaseEntity {
///...
public static Cart createCart(Member member) {
Cart cart = Cart.builder()
.member(member)
.build();
return cart;
}
}
장바구니에 담을 상품 엔티티를 생성하는 메소드와 장바구니에 담을 수량을 증가시켜 주는 메소드를 CartItem 클래스에 추가합니다.
package me.jincrates.gobook.domain.carts;
//...
@NoArgsConstructor
@Getter
@Table(name = "cart_item")
@Entity
public class CartItem extends BaseEntity {
//...
public static CartItem createCartItem(Cart cart, Item item, int count) {
CartItem cartItem = CartItem.builder()
.cart(cart)
.item(item)
.count(count)
.build();
return cartItem;
}
public void addCount(int count) {
this.count += count;
}
}
현재 로그인한 회원의 Cart 엔티티를 찾기 위해서 쿼리 메소드를 추가합니다.
package me.jincrates.gobook.domain.carts;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CartRepository extends JpaRepository<Cart, Long> {
Cart findByMemberId(Long memberId);
}
package me.jincrates.gobook.domain.carts;
import me.jincrates.gobook.web.dto.CartDetailDto;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface CartItemRepository extends JpaRepository<CartItem, Long> {
CartItem findByCartIdAndItemId(Long cartId, Long itemId);
}
장바구니에 상품을 담는 로직을 작성합니다.
package me.jincrates.gobook.service;
import lombok.RequiredArgsConstructor;
import me.jincrates.gobook.domain.carts.Cart;
import me.jincrates.gobook.domain.carts.CartItem;
import me.jincrates.gobook.domain.carts.CartItemRepository;
import me.jincrates.gobook.domain.carts.CartRepository;
import me.jincrates.gobook.domain.items.Item;
import me.jincrates.gobook.domain.items.ItemRepository;
import me.jincrates.gobook.domain.members.Member;
import me.jincrates.gobook.domain.members.MemberRepository;
import me.jincrates.gobook.domain.orders.Order;
import me.jincrates.gobook.web.dto.CartDetailDto;
import me.jincrates.gobook.web.dto.CartItemDto;
import me.jincrates.gobook.web.dto.CartOrderDto;
import me.jincrates.gobook.web.dto.OrderDto;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thymeleaf.util.StringUtils;
import javax.persistence.EntityNotFoundException;
import java.util.ArrayList;
import java.util.List;
@Transactional
@RequiredArgsConstructor
@Service
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();
}
}
}
package me.jincrates.gobook.web;
import lombok.RequiredArgsConstructor;
import me.jincrates.gobook.service.CartService;
import me.jincrates.gobook.web.dto.CartDetailDto;
import me.jincrates.gobook.web.dto.CartItemDto;
import me.jincrates.gobook.web.dto.CartOrderDto;
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;
@RequiredArgsConstructor
@Controller
public class CartController {
private final CartService cartService;
@PostMapping(value = "/cart")
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);
}
}
package me.jincrates.gobook.service;
import me.jincrates.gobook.domain.carts.CartItem;
import me.jincrates.gobook.domain.carts.CartItemRepository;
import me.jincrates.gobook.domain.items.Item;
import me.jincrates.gobook.domain.items.ItemRepository;
import me.jincrates.gobook.domain.items.ItemSellStatus;
import me.jincrates.gobook.domain.members.Member;
import me.jincrates.gobook.domain.members.MemberRepository;
import me.jincrates.gobook.web.dto.CartItemDto;
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.*;
@SpringBootTest
@Transactional
@TestPropertySource(properties = {"spring.config.location=classpath:application-test.yml"})
public class CartServiceTest {
@Autowired
ItemRepository itemRepository;
@Autowired
MemberRepository memberRepository;
@Autowired
CartService cartService;
@Autowired
CartItemRepository cartItemRepository;
public Item saveItem() {
Item item = Item.builder()
.itemNm("테스트 상품")
.price(10000)
.itemDetail("테스트 상품 설명")
.itemSellStatus(ItemSellStatus.SELL)
.stockNumber(100)
.build();
return itemRepository.save(item);
}
public Member saveMember() {
Member member = Member.builder()
.email("test@email.com")
.build();
return memberRepository.save(member);
}
@Test
@DisplayName("장바구니 담기 테스트")
public void addCart() {
Item item = saveItem();
Member member = saveMember();
CartItemDto cartItemDto = CartItemDto.builder()
.itemId(item.getId())
.count(5)
.build();
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());
}
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/default}">
<!-- 사용자 스크립트 추가 -->
<th:block layout:fragment="script">
<script th:inline="javascript">
<!-- 코드 생략 -->
function addCart() {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
var url = "/cart";
var paramData = {
itemId : $("#itemId").val(),
count : $("#count").val()
};
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 = '/';
},
error: function(jqXHR, status, error) {
if (jqXHR.status == '401') {
alert('로그인 후 이용해주세요.');
location.href = '/members/login';
} else {
alert(jqXHR.responseText);
}
}
});
}
</script>
</th:block>
<!-- 코드 생략 -->
<button type="button" class="btn btn-light border btn-outline-dark btn-lg" onclick="addCart()">장바구니 담기</button>
<!-- 코드 생략 -->
</html>