🌿SPRING/🌱연습[SPRING]

[SPRING] 클론코딩 장바구니 로직 만들기

디카페인라떼 2022. 9. 15. 11:12

이번 프로젝트는 IKEA 클론 코딩이었다. 일주일이라는 시간동안 해내는거라 스코프는 최대한 작게 했다.

그 중에 나는 장바구니 .. 뭔가 특별하게 어려운가? 싶어서 인터넷에서 레퍼코드를 찾아보겠다고 하루를 할애했다..

실제로 찾기는 했으나 원래 하던 방식이 아니라서 또 멘붕.......

결국에는 그냥 알던대로 하기로 했다 ..^^ㅋㅋㅎ

 


1. Entity 설정
  • 기획 시 ERD를 설계했던대로 엔티티를 설정해준다.
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Cart extends Timestamped{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name ="member_id")
    private Member member;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name ="product_id")
    private Product product;

    @Column(nullable = false)
    private int count;//카트에 담긴 상품 개수

    //금액은 BigDecimal 사용하기
    @Column(nullable = false)
    private BigDecimal sum; //상품비용 합계

    @Column(nullable = false)
    private BigDecimal  delivery_fee; //배송비

그리고 나는 @Builder 방식보다는 생성자 방식을 선호해서... (나중에 대참사가 일어남..)

public Cart (CartRequestDto cartRequestDto,Member member, Product product){
    this.product = product;
    this.member = member;
    this.count = cartRequestDto.getCount();
    this.sum =product.getPrice().multiply(new BigDecimal(count));
    this.delivery_fee = new BigDecimal(count * 15000);
}

 


 

2. Repository 설정
  • 로직을 짜면서 계속 추가하긴했지만 결론적으론 이렇게 사용하였다
public interface CartRepository extends JpaRepository<Cart, Long> {

  List<Cart> findByMember(Member member);

  Optional<Cart> findByIdAndMember(Long id, Member member);

  void deleteByMember(Member member);

  List<Cart> findByMemberAndProduct(Member member, Product product);

}

 


 

3. Dto 설정
  • 장바구니 상품 수량 변경시 필요한 requestDto 설정
@Getter
public class CartRequestDto {

  @NotNull
  private Long productId;
  private int count;

}
  • ResponseDto 설정
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CartResponseDto {

  private Product product;
  private Long cart_id;
  private  int count;
  private  BigDecimal cart_price;// 카트 당 합계 금액
  private LocalDateTime createdAt;

}

👉 (나중에) 수정한 내용이지만 생성자 방식에서 빌더 방식으로 리팩토링을 거쳤다.. 그래서 Response에 빌더가 있고 생성자는 따로 안해주었다.

 


 

4. Service
  • 본격적인 로직짜기 ! PK값과 FK 값에 대해서 많이 헷갈렸다. 이래서 DB를 먼저 공부하라고했나봐..
  • Cart_id 가 멤버당 1개씩 지정되는 줄 알았는데 그게 아니고 멤버의 장바구니의 상품 당 cart_id가 하나씩 부여되는 식이었다 (PK값이므로) => 그래서 repository로 찾아오는데 꽤 헷갈렸다..

 

  • 장바구니 상품 추가

👉 단순하게 추가가 아니라 이미 있는 상품인지 아닌지 확인하는 로직을 추가해야 했다!

  @Transactional // get빼고는 트랜잭션 꼭 넣어주기!
  @Override
  public CartResponseDto addCart(CartRequestDto cartRequestDto,Member member, Product product){
  
    List<Cart> cartList = cartRepository.findByMemberAndProduct(member, product);
    //사용자와 선택한 상품으로 일단 카트리스트를 만들고
    Cart cart;
    
    //장바구니에 상품이 없을 때
    if (cartList.isEmpty()) {
      cart = new Cart(cartRequestDto,member,product);//카트를 새로 생성해서
      cart = cartRepository.save(cart);//저장해준다
      
    } else {//장바구니에 상품이 있을 때
    
      cart = cartList.get(0);//카트리스트의 index 0번째 값 =  처음에 찾은 cart 상품
      cart.changeCount(cart.getCount()+1); // 같은 상품이므로 갯수를 하나더 늘려준다.
    }
    cartRepository.save(cart);
    
    return CartResponseDto.builder()//ResponseDto 빌더써서 저장해주기..
        .cart_id(cart.getId())
        .product(cart.getProduct())
        .count(cartRequestDto.getCount())
        .cart_price(cart.getSum())
        .createdAt(cart.getCreatedAt())
        .build();
  }

 

  • 장바구니 조회 [처음]
  @Override
  public List<CartResponseDto> getCartList(Member member) {
    return cartRepository.findByMember(member);
  }

👉그냥 단순하게 List형으로 조회만 하면되겠지 ..^^ 하다가 해보니 아니었다..

이상태로 조회를 하게되면 상품이 여러개일때 응답 값이 문제였다

 

 

👉이렇게  상품 합계와 배송비가 그냥 따로 나올뿐 총 배송비/ 총 상품금액은 생각을 못했다...... 테스트 데이터를 넣어서 해볼때 상품을 하나씩만 넣어놓고 해서 이 부분을 아예 생각하지 못했다..

 

🚨data 안에 product를 list로 넣을 순 없을까? => cart_id가 따로 부여되는 부분은 어떡하지?

    하면서 고민하면서 CartResponseDto를 수정하려고 하니 .... 여기서 문제가 생겼다

🤦‍♀️아까 언급했다 싶이 나는 생성자 방식 vs 다른 기능들은 빌더 방식이라 생성자 방식으로 수정하려면 다른 빌더 방식을 모두 생성자 방식으로 리팩토링해야했다..... 

✔ 결국 내 코드들을 모두 빌더 방식으로 리팩토링하기로 결정! 하고 전체조회시 전체를 감싸주는 CartGetListReponseDto를 새로 만들어 주어서 전체 총 배송비와 총 금액을 나타낼수있게 만들었다.

 

  • 장바구니 조회 [수정 후]
  @Override
  public CartGetListResponseDto getCartList(Member member) {
    List<Cart> carts = cartRepository.findByMember(member);
    List<CartResponseDto> cartProducts = new ArrayList<>();

    int totalCnt = 0; //총 상품 갯수 초기화
    BigDecimal totalPrice = new BigDecimal(0);
    for (Cart cart : carts) {//for문을 돌려서
      cartProducts.add(//List에
          CartResponseDto.builder()//cartresponseDto를 넣어준다
              .cart_id(cart.getId())
              .product(cart.getProduct())
              .count(cart.getCount())
              .cart_price(cart.getProduct().getPrice().multiply(new BigDecimal(cart.getCount())))
              .createdAt(cart.getCreatedAt())
              .build());
              
      totalCnt += cart.getCount();//총 상품 갯수를 더해주고
      totalPrice = totalPrice.add(cart.getProduct().getPrice().multiply(new BigDecimal(cart.getCount())));
    }//총금액 공식을 넣어준다

//전체상품조회Dto에도 값을 넣어준다
    CartGetListResponseDto cartGetListResponseDto = CartGetListResponseDto.builder()
        .cartProducts(cartProducts)//위에 지정했던 List를 넣어주고
        .total_delivery_fee(new BigDecimal(15000 * totalCnt))//총 배송비
        .total_order_price(totalPrice)//총 금액
        .total_order_and_delivery_price(totalPrice.add(new BigDecimal(15000 * totalCnt)))
        .build();//총 배송비 + 총 제품비

    return cartGetListResponseDto;
  }

이렇게 해주고 나니 

👉무사히 감싸져서 전체금액이 잘 나왔다......

product부분이 list로 나와야한다는 생각 때문에 그걸 감싸줄 더 큰 부분을 생각하지 못했다...

🚩=> 처음에 만들때부터 정확히 어떤 게 응답값으로 나가야할지 잘 계산해서 만들어야겠다!!

 

 

  • 상품 수량 변경 
@Transactional
@Override
public CartResponseDto changeItemCount(Long id, Member member, CartRequestDto cartRequestDto) {
  Cart cart = cartRepository.findByIdAndMember(id,member)//cart_id와 멤버로 찾아서
      .orElseThrow(EntityNotFoundException::new);
      
  cart.changeCount(cartRequestDto.getCount());// 리퀘스트로 받은 갯수로 변경해주고
  cartRepository.save(cart);// 저장해준다
  
  return CartResponseDto.builder()//리턴해주는 Response는 빌더로.. 아님 message로 success만 넘겨도 됨
      .cart_id(cart.getId())
      .product(cart.getProduct())
      .count(cartRequestDto.getCount())
      .cart_price(cart.getSum())
      .createdAt(cart.getCreatedAt())
      .build();
}

 

  • 상품 삭제 / 장바구니 전체 삭제 
@Transactional
@Override
public void deleteOneItem(Long id) { //cart_id
  cartRepository.deleteById(id);
}

//장바구니 전체상품 삭제하기
@Transactional
@Override
public void deleteAllItem(Member member) {
  cartRepository.deleteByMember(member);
}

 


5. controller 설정

👉그냥 넣으면되니까 생략...

 

모든 소스코드는 github에서 확인 가능하다!


  • 다 완성하고나서 Security부분과 연결하고 테스트를 해보니 JPA 연관관계 때문에 애를 많이 먹었다...
    • 특히 삭제 .. 부분에서 외래키때문에 삭제가 안된다는 에러를 계속봤다.. 
  • 장바구니는 사용자와 상품 모두와 관계가 있기 때문에 많이 헷갈렸다.. 시작 전에 시간을 가지고 그걸 먼저 이해했다면 괜찮았을 것 같다
  • 괜히 쫄지말고 레퍼코드 먼저 찾지말고 원래 하던대로 시작해볼걸 그랬다. 원래 알고있던 CRUD에서 살을 붙이면 (로직을 더하면) 될 일이었다.. 쫄지말자~~!
🚨관련 에러 노트

2022.09.13 - [SPRING/🚨에러노트[SPRING]] - [220913] UserDetails = null 에러

 

[220913] UserDetails = null 에러

문제상황 클론코딩을 하던 중 장바구니 crud 에서 @AuthenticationPrincipal을 썼으나 UserDetails가 안불러와졌다. 해결방법 securityfilterchain 에서 url을 지정해 두어서 아예 필터가 안먹혀서 null값이 들..

wearegolden.tistory.com

2022.09.13 - [SPRING/🚨에러노트[SPRING]] - [220913] not-null property references a null or transient value 에러

 

[220913] not-null property references a null or transient value 에러

문제상황 usesrdetails를 가지고 왔으나 여전히 에러 발생 null이 들어가면 안되는 곳에 null 값이..? 배송비 부분을 설정 안했나..?? BigDecimal 인데 생성자에서 지정을 안해줬다... 더보기-BigDecimal 이란..

wearegolden.tistory.com

2022.09.13 - [SPRING/🚨에러노트[SPRING]] - [220913] The given id must not be null! 에러

 

[220913] The given id must not be null! 에러

문제상황 장바구니 상품 삭제시에 에러 발생으로 넘어갔다... 콘솔에 뜬 에러 허전...해 보인다..^^ 해결방법 @PathVariable 이 없다..^^

wearegolden.tistory.com

2022.09.13 - [SPRING/🚨에러노트[SPRING]] - [220913] JPA delete 관련 에러 (Cascade 영속성 전이 관련 에러)

 

[220913] JPA delete 관련 에러 (Cascade 영속성 전이 관련 에러)

문제상황 여전히 삭제 시 에러.. 연관관계 관련 에러인것 같다.. 외래키가 설정이 되어 있어서 같이 삭제가 안된다 연관관계 @OneToMany 인 부분에 cascade 연관관계 및 orphanRemoval 설정도 다 되어있었

wearegolden.tistory.com