본문 바로가기
web/Bakery Shop Project

쇼핑몰 구현 6 - 라이브러리 설치, Entity 생성

by su0a 2024. 2. 23.

1. 프로젝트에 사용할 라이브러리 설치 (build.gradle)

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
    
	//security
	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.2.RELEASE'
	implementation 'org.springframework.boot:spring-boot-starter-security'
    
	// Email
	implementation 'org.springframework.boot:spring-boot-starter-mail'
    
	//db
	runtimeOnly 'com.mysql:mysql-connector-j'
	implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    
	//lombok
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
    
	//aws
	implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
}

 

 

2. 엔티티 생성

 

2.1. Member

  • member는 여러개의 order, qna, review, like, cartItem을 가질 수 있음 => 1 : N
  • member는 한개의 delivery를 가질 수 있음 => 1 : 1
  • member가 삭제되면 qna, review, like, cart 모두 삭제 => orphanRemoval = true 설정
  • membership은 @Enumerated(EnumType.STRING)을 설정 => db에 저장될 때 string 타입으로 저장
  • edit 메소드 => 회원정보 수정시 사용
  • deliveryCreate 메소드 => 새로운 배송지 등록할 때 사용
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Table(name = "member")
public class Member {

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String email; //로그인에 사용되는 id

    private String name; //유저이름

    private String password; //비밀번호

    @OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
    private Delivery delivery; //주소

    @Enumerated(EnumType.STRING)
    private Membership membership; //권한

    @OneToMany(mappedBy = "member")
    private List<Order> orders; //주문목록

    @OneToMany(mappedBy = "member",orphanRemoval = true)
    private List<QnA> QnAs; //작성한 문의 목록

    @OneToMany(mappedBy = "member", orphanRemoval = true)
    private List<Review> reviews;//작성한 리뷰 목록

    @OneToMany(mappedBy = "member",orphanRemoval = true)
    private List<Like> likes; //위시리스트

    @OneToMany(mappedBy = "member",orphanRemoval = true)
    private List<Cart> cartItems; //장바구니목록

    //회원수정
    public void edit(String newPassword,String name){
        this.password=newPassword;
        this.name=name;
    }

    public void deliveryCreate(Delivery newDelivery){
        this.delivery=newDelivery;
    }
}

 

2.2. Membership

public enum Membership {
    FRIEND, BUDDY, SOULMATE, ADMIN
}

 

2.3. Address

  • 임베디드 타입 사용
  • Delivery 테이블에 컬럼으로 존재
@Embeddable
@Getter
public class Address {
    private String zipcode;
    private String streetAddress;
    private String detailAddress;

    public Address() {
    }

    public Address(String zipcode, String streetAddress, String detailAddress) {
        this.zipcode = zipcode;
        this.streetAddress = streetAddress;
        this.detailAddress = detailAddress;
    }

    public void addressEdit(String zipcode, String streetAddress, String detailAddress) {
        this.zipcode = zipcode;
        this.streetAddress = streetAddress;
        this.detailAddress = detailAddress;
    }
}

 

2.4. Delivery

  • address를 내장
  • 주문, 회원과 일대일 매핑 => 1 : 1
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class Delivery {
    @Id @GeneratedValue
    private Long id;

    @OneToOne(fetch = FetchType.LAZY,mappedBy ="delivery")
    private Member member; //회원

    @OneToOne(mappedBy = "delivery",fetch = FetchType.LAZY)
    private Order order; //주문

    private String deliveryName; //배송지명
    private String memberName;

    @Embedded
    private Address address; //배송주소

    private String phoneNumber; //핸드폰 번호

}

 

2.5. DeliveryStatus

public enum DeliveryStatus {
    READY, START, COMPLETE
}

 

2.6. Item

  • itemCategory는 @Enumerated(EnumType.STRING)을 설정 => db에 저장될 때 string 타입으로 저장
  • 한개의 Item은 여러개의 UploadImage를 가질 수 있음 => 1 : N
  • modifyStock 메소드 => 상품 수정 시 재고 변경할 때 사용
  • removeStock 메소드 => 상품 주문 시 재고 줄일 때 사용 => 재고가 주문한 수량보다 적으면 수량부족예외 발생
  • addStock 메소드 => 상품 주문 취소 시 재고 늘릴 때 사용
  • edit 메소드 => 상품 Entity 수정 시 사용
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class Item {
    @Id @GeneratedValue
    private Long id;
    private String name; //상품 이름
    private String sizeTip; //사이즈 설명
    private String itemTip; //상품 설명
    private Integer delivery_price; //배송비
    private Integer price; //상품 가격
    @Enumerated(EnumType.STRING)
    private ItemCategory itemCategory; //상품 종류

    private Integer stockQuantity; //재고

    @OneToMany(mappedBy = "item", orphanRemoval = true)
    private List<UploadImage> uploadImages; //상품을 설명하는 이미지


    //==비즈니스 로직==//
    public void modifyStock(int quantity){
        this.stockQuantity =quantity;
    }

    public void removeStock(int quantity){
        int restStock = stockQuantity-quantity;
        if(restStock<0){
            throw new NotEnoughStockException("need more stock");
        }
        this.stockQuantity = restStock;
    }

    public void addStock(int quantity){
        this.stockQuantity+=quantity;
    }

    public Item edit(Integer price,ItemCategory itemCategory,Integer stock ,String itemTip, String sizeTip) {
        this.price=price;
        this.itemTip=itemTip;
        this.sizeTip=sizeTip;
        modifyStock(stock);
        return  this;
    }

}

 

2.7. ItemCategory

  • 총 7개의 카테고리로 구성
  • of 메소드 => 대소문자를 구분하지 않고 카테고리를 찾기 위해 사용
public enum ItemCategory {
    CAKE, TARTE , MADELEINE, FINANCIER, MACARON, SCONE, MUFFIN;

    public static ItemCategory of(String category){
        if(category.equalsIgnoreCase("cake")){
            return CAKE;
        } else if(category.equalsIgnoreCase("tarte")){
            return TARTE;
        } else if(category.equalsIgnoreCase("madeleine")){
            return MADELEINE;
        } else if(category.equalsIgnoreCase("financier")){
            return FINANCIER;
        } else if(category.equalsIgnoreCase("macaron")){
            return MACARON;
        } else if(category.equalsIgnoreCase("scone")){
            return SCONE;
        } else if(category.equalsIgnoreCase("muffin")){
            return MUFFIN;
        }
        else return null;
    }
}

 

2.8. Cart

  • increaseCount 메소드 => 해당 상품이 이미 장바구니에 존재하는 경우 해당 상품의 count만 증가
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class Cart {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Member member;

    @ManyToOne(fetch = FetchType.LAZY)
    private Item item;

    private Integer count;

    public static Cart create(Item item, int count, Member member){
        return Cart.builder()
                .item(item)
                .count(count)
                .member(member)
                .build();
    }

    public boolean increaseCount(int cnt){
        this.count+=cnt;
        return true;
    }
}

 

2.9. Review

  • 한개의 review는 한개의 UploadImage를 가질 수 있음
  • saveMemberItem 메소드 => 리뷰 생성 시 reviewJoinRequest를 통해 review를 만드는데 reviewJoinRequest DTO는 Member와 Item에 대한 정보가 없으므로 해당 정보를 따로 추가해주기 위해 사용
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class Review extends BaseEntity{
    @Id @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Member member;

    @ManyToOne(fetch = FetchType.LAZY)
    private Item item;

    private Integer rating; //별점

    private String title;
    private String content;

    @OneToOne(fetch = FetchType.LAZY,mappedBy = "review", orphanRemoval = true)
    private UploadImage uploadImage;

    public void saveMemberItem(Member member,Item item){
        this.member=member;
        this.item=item;
        member.getReviews().add(this);
    }

    public Review setUploadImage(UploadImage uploadImage) {
        this.uploadImage=uploadImage;
        return this;
    }
}

 

2.10. QnA

  • 해당 Q&A에 답변이 등록되면 answerStatus가 COMPLETE으로 변경
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class QnA extends BaseEntity{
    @Id @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Member member; //글을 작성한 회원

    @ManyToOne(fetch = FetchType.LAZY)
    private Item item; //어떤 상품에 대한 글인지 확인하기 위해

    private String title; //제목
    private String content; //내용

    private String answer;

    @Enumerated(EnumType.STRING)
    private AnswerStatus answerStatus; //답변 상태

    @OneToOne(fetch = FetchType.LAZY,mappedBy = "qna", orphanRemoval = true)
    private UploadImage uploadImage;

    public void saveMemberItem(Member loginMember, Item item) {
        this.member=loginMember;
        this.item=item;
        loginMember.getQnAs().add(this);
    }
    public QnA setUploadImage(UploadImage uploadImage) {
        this.uploadImage=uploadImage;
        return this;
    }

    public QnA createAnswer(String answer) {
        this.answer=answer;
        this.answerStatus=AnswerStatus.COMPLETE;
        return this;
    }
}

 

2.11. AnswerStatus

public enum AnswerStatus {
    READY, COMPLETE
}

 

2.12. Order

  • addOrderItem 메소드 => 주문에 OrderItem을 추가하면서 OrderItem에도 해당 주문을 추가 (양방향 연관관계)
  • createorder 메소드 => 주문 생성 시 사용
  • getTotalPrice 메소드 => 총 주문 금액 구할 때 사용
  • changeDeliveryStatus = > 관리자가 배송시작 버튼 누를 때 호출됨
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Table(name = "orders")
public class Order extends BaseEntity{
    @Id @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
    @JoinColumn(name = "member_id")
    private Member member; //주문한 회원

    private Integer totalPrice;

    @OneToMany(mappedBy = "order",orphanRemoval = true, cascade = CascadeType.ALL)
    private List<OrderItem> orderItems; //order과 item은 다대다 관계이므로 orderItem 만들어 일대다 관계로 변경
    
    @OneToOne(fetch = FetchType.LAZY)
    private Delivery delivery; //배송정보

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus; //주문상태(주문,취소)

    @Enumerated(EnumType.STRING)
    private DeliveryStatus deliveryStatus; //배송상태


    public void addOrderItem(OrderItem orderItem){
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }
    public static Order createOrder(Member member,List<OrderItem> orderItemList,Delivery delivery){
        Order order = Order.builder()
                .member(member).
                orderStatus(OrderStatus.ORDER)
                .deliveryStatus(DeliveryStatus.READY)
                .delivery(delivery).build();
        order.orderItems=new ArrayList<>();
        for(OrderItem orderItem: orderItemList){
            order.addOrderItem(orderItem);
        }
        order.totalPrice=order.getTotalPrice(orderItemList);
        return order;
        
    }
    public int getTotalPrice(List<OrderItem>orderItems){
        int totalPrice=0;
        for(OrderItem orderItem:orderItems){
            totalPrice += orderItem.getTotalPrice();
        }
        return totalPrice;
    }

    public boolean cancel() {
        if(this.getDeliveryStatus()==DeliveryStatus.READY){
            this.orderStatus=OrderStatus.CANCEL;
            for(OrderItem orderItem:orderItems){
                Integer count = orderItem.getCount();
                orderItem.getItem().addStock(count);
            }
            return true;
        }
        else{
            return false;
        }
    }

    public boolean changeDeliveryStatus(){
        this.deliveryStatus=DeliveryStatus.START;
        return true;
    }
}

 

2.13. OrderStatus

public enum OrderStatus {
    ORDER, CANCEL
}

 

2.14. OrderItem

  • Order과 Item이 다대다 매핑이므로 일대다 매핑으로 만들기 위해 중간 엔티티 생성
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class OrderItem {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Order order;

    @ManyToOne(fetch = FetchType.LAZY)
    private Item item;

    private Integer orderPrice; //주문했을때의 상품 가격
    private Integer count; //상품 개수

    public static OrderItem createOrderItem(Item item,int count){
        System.out.println(item.getId()+" "+count);
       item.removeStock(count);

        return OrderItem.builder()
                .item(item)
                .count(count)
                .orderPrice(item.getPrice())
                .build();

    }
    public int getTotalPrice(){
        return orderPrice*count;
    }

    public void setOrder(Order order) {
        this.order=order;
    }
}

 

2.15. UploadImage

  • 이미지 저장할 때 사용할 클래스
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
public class UploadImage {

    @Id @GeneratedValue
    private Long id;

    private String originalFilename; //원본 파일명
    private String savedFilename; //서버에 저장된 파일명

    @ManyToOne(fetch = FetchType.LAZY)
    private Item item;

    @OneToOne(fetch = FetchType.LAZY)
    private Review review;

    @OneToOne(fetch = FetchType.LAZY)
    private QnA qna;

}

 

2.16. BaseEntity

  • 시간을 저장해야 할 때 사용할 클래스
@Getter
@MappedSuperclass //추상클래스에 사용할 수 있으며 엔티티가 될 수 없고 상속을 통해서 사용해야 함
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {

    @CreatedDate //jpa 저장소가 save()할 때 자동으로 생성시간을 만듦
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate //jpa 저장소가 수정할 때 자동으로 생성시간을 만듦
    private LocalDateTime lastModifiedAt;
}