관리 메뉴

거니의 velog

(43) 장바구니 API 만들기 1 본문

SpringBoot_React 풀스택 프로젝트

(43) 장바구니 API 만들기 1

Unlimited00 2024. 3. 8. 14:17

* 사용자의 인증에 대한 처리가 완료되었다면 로그인한 사용자들이 사용할 수 있는 장바구니 기능을 구현해 보도록 하자. 장바구니는 JPA의 연관관계를 이용해서 구성하고 @Query를 이용해서 현재 사용자의 장바구니가 가진 상품목록과 수량(장바구니 아이템)들을 반환하는 기능을 구현한다.

* 이번 장의 학습 목표는 다음과 같다.

1. JPA의 연관관계를 이용한 장바구니 설계

2. JPQL을 이용한 조인 처리와 Projections를 이용한 DTO 처리

3. 스프링 시큐리티와 로그인 정보를 활용한 사용자 인증 처리

1. 장바구니 엔티티의 설계

* 장바구니는 사용자 한 명당 하나의 장바구니를 가지도록 구성되고 하나의 장바구니에는 여러 개의 상품과 해당 상품(장바구니 아이템)의 수량을 넣을 수 있도록 구성된다. 이러한 관계를 다이어그램으로 표현하면 아래와 같은 구조가 된다.

- 사용자(member)와 장바구니(tbl_cart)는 일대일의 관계로 연결된다.

- 하나의 장바구니(tbl_cart)는 여러 개의 장바구니 아이템(tbl_cart_item)을 담을 수 있다.

- 장바구니 아이템은 상품(tbl_product)과 수량, 장바구니의 번호로 구성된다.

- 하나의 상품은 여러 개의 이미지(product_image_list)를 가지고 있다.

(1) 장바구니 관련 엔티티

* 장바구니 엔티티는 특정한 사용자(member)의 정보를 일대일(OneToOne)의 관계로 설정하게 된다. 프로젝트 내 domain 패키지에 Cart 클래스를 추가한다.

package com.unlimited.mallapi.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * 장바구니 엔터티 클래스입니다.
 *
 * - 장바구니에 대한 정보를 담고 있습니다.
 * - 한 회원은 하나의 장바구니를 소유합니다.
 *
 * @Entity: JPA 엔터티로 지정
 * @Table: 데이터베이스에서 사용할 테이블 정보 설정
 * @Id: 엔터티의 주키를 나타냄
 * @GeneratedValue: 주키의 생성 전략을 지정 (IDENTITY: 자동 증가)
 * @OneToOne: 일대일 관계를 설정
 * @JoinColumn: 외부 키를 지정하여 조인
 * @AllArgsConstructor: 모든 필드를 포함하는 생성자를 자동으로 생성하는 Lombok 애노테이션
 * @NoArgsConstructor: 파라미터가 없는 기본 생성자를 자동으로 생성하는 Lombok 애노테이션
 * @Builder: 빌더 패턴을 사용할 수 있도록 하는 Lombok 애노테이션
 * @Getter: 필드에 대한 getter 메서드를 자동으로 생성하는 Lombok 애노테이션
 * @ToString: toString() 메서드를 생성하는 Lombok 애노테이션
 */
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString(exclude = "owner") // 'owner' 필드를 toString()에서 제외
@Table(name = "tbl_cart", indexes = { @Index(name = "idx_cart_email", columnList = "member_owner") })
public class Cart {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long cno; // 장바구니 고유 식별 번호

    @OneToOne
    @JoinColumn(name = "member_owner") // 'member_owner' 컬럼으로 조인
    private Member owner; // 장바구니 소유 회원

}

* Cart 엔티티 클래스는 tbl_cart 라는 이름의 테이블로 생성하고 주로 사용자의 이메일을 통해서 검색하게 되므로 @Index를 이용해서 테이블 내에 인덱스를 생성한다.

* 장바구니 아이템(CartItem)은 상품(Product)과 수량(qty)을 속성으로 가지고 Cart와는 다대일(ManyToOne)의 관계로 tbl_cart_item 이름의 테이블로 생성한다.

package com.unlimited.mallapi.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 * 장바구니 상품 엔터티 클래스입니다.
 *
 * - 장바구니에 담긴 각 상품의 정보를 나타냅니다.
 * - 여러 개의 장바구니 상품은 하나의 장바구니에 속합니다.
 * - 각 장바구니 상품은 하나의 상품을 참조합니다.
 *
 * @Entity: JPA 엔터티로 지정
 * @Table: 데이터베이스에서 사용할 테이블 정보 설정
 * @Id: 엔터티의 주키를 나타냄
 * @GeneratedValue: 주키의 생성 전략을 지정 (IDENTITY: 자동 증가)
 * @ManyToOne: 다대일 관계를 설정
 * @JoinColumn: 외부 키를 지정하여 조인
 * @AllArgsConstructor: 모든 필드를 포함하는 생성자를 자동으로 생성하는 Lombok 애노테이션
 * @NoArgsConstructor: 파라미터가 없는 기본 생성자를 자동으로 생성하는 Lombok 애노테이션
 * @Builder: 빌더 패턴을 사용할 수 있도록 하는 Lombok 애노테이션
 * @Getter: 필드에 대한 getter 메서드를 자동으로 생성하는 Lombok 애노테이션
 * @ToString: toString() 메서드를 생성하는 Lombok 애노테이션
 */
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString(exclude = "cart") // 'cart' 필드를 toString()에서 제외
@Table(name = "tbl_cart_item", indexes = {
        @Index(columnList = "cart_cno", name = "idx_cartitem_cart"),
        @Index(columnList = "product_pno, cart_cno", name = "idx_cartitem_pno_cart") })
public class CartItem {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long cino; // 장바구니 상품 고유 식별 번호

    @ManyToOne
    @JoinColumn(name = "product_pno") // 'product_pno' 컬럼으로 조인
    private Product product; // 장바구니 상품에 해당하는 상품 정보

    @ManyToOne
    @JoinColumn(name = "cart_cno") // 'cart_cno' 컬럼으로 조인
    private Cart cart; // 해당 장바구니 상품이 속한 장바구니 정보

    private int qty; // 장바구니에 담긴 상품 수량

    /**
     * 상품 수량을 변경합니다.
     *
     * @param qty 변경할 상품 수량
     */
    public void changeQty(int qty) {
        this.qty = qty;
    }

}

* CartItem의 경우 수정할 수 있는 것이 수량 정도이므로 changeQty() 만을 작성한다. 장바구니의 아이템은 cino라는 PK가 있긴 하지만 특정한 상품이 특정한 장바구니에 있는지 조회하는 기능이 필요할 수 있으므로 인덱스를 설정해 둔다.


2. 장바구니 DTO의 설정

* 리액트 애플리케이션에서 새로운 상품의 등록이나 상품의 수량 등을 변경하는 등의 작업을 위해서 전달되는 데이터들은 dto 패키지 내에 CartItemDTO 클래스로 작성한다.

* CartItemDTO는 다음과 같은 상황에서 사용된다.

- 상품을 조회하는 화면에서 사용자가 자신의 장바구니에 상품을 추가하는 경우 : 전달되는 데이터는 사용자의 이메일,
  추가하고 싶은 상품의 번호, 수량
  
- 장바구니 아이템 목록에서 상품 수량을 조정하는 경우 : 이미 만들어진 장바구니 아이템 번호(cino),
  변경하고자 하는 수량

package com.unlimited.mallapi.dto;

import lombok.Data;

/**
 * 장바구니 상품 데이터 전송 객체(DTO) 클래스입니다.
 *
 * - 클라이언트와 서버 간에 장바구니 상품 정보를 주고받을 때 사용됩니다.
 *
 * @Data: Lombok 애노테이션으로, 클래스 내의 필드들에 대한 Getter, Setter, toString(), equals(),
 *        hashCode()를 자동으로 생성합니다.
 */
@Data
public class CartItemDTO {

    private String email; // 회원 이메일
    private Long pno; // 상품 고유 식별 번호
    private int qty; // 상품 수량
    private Long cino; // 장바구니 상품 고유 식별 번호

}

* 컨트롤러로 전달되는 목록 데이터는 특정 사용자의 장바구니에 포함된 상품의 정보들과 수량, 이미지 파일들이다. 이를 CartItemListDTO 클래스로 정의한다.

package com.unlimited.mallapi.dto;

import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 장바구니 상품 목록 데이터 전송 객체(DTO) 클래스입니다.
 *
 * - 클라이언트에게 전달되거나 클라이언트로부터 받는 장바구니 상품 목록 정보를 담고 있습니다.
 *
 * @Data: Lombok 애노테이션으로, 클래스 내의 필드들에 대한 Getter, Setter, toString(), equals(),
 *        hashCode()를 자동으로 생성합니다.
 * @Builder: 빌더 패턴을 사용할 수 있도록 하는 Lombok 애노테이션
 * @NoArgsConstructor: 파라미터가 없는 기본 생성자를 자동으로 생성하는 Lombok 애노테이션
 */
@Data
@Builder
@NoArgsConstructor
public class CartItemListDTO {

    private Long cino; // 장바구니 상품 고유 식별 번호
    private int qty; // 상품 수량
    private Long pno; // 상품 고유 식별 번호
    private String pname; // 상품 이름
    private int price; // 상품 가격
    private String imageFile; // 상품 이미지 파일명

    /**
     * 모든 필드를 초기화하는 생성자입니다.
     *
     * @param cino      장바구니 상품 고유 식별 번호
     * @param qty       상품 수량
     * @param pno       상품 고유 식별 번호
     * @param pname     상품 이름
     * @param price     상품 가격
     * @param imageFile 상품 이미지 파일명
     */
    public CartItemListDTO(Long cino, int qty, Long pno, String pname, int price, String imageFile) {
        this.cino = cino;
        this.qty = qty;
        this.pno = pno;
        this.pname = pname;
        this.price = price;
        this.imageFile = imageFile;
    }

}

* CartItemListDTO에는 특이하게도 직접 생성자를 정의하는데 이는 JPQL을 이용해서 직접 DTO 객체를 생성하는 Projection이라는 방식을 이용하기 위함이다.