@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // new 생성자 생성 못하게 -> Member member = new Member()
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String name;
private String email;
private String password;
@Embedded
private Address address;
@Enumerated(value = EnumType.STRING)
private Role role;
@Builder
private Member(String name, Address address, String email, String password, Role role) {
this.name = name;
this.address = address;
this.email = email;
this.password = password;
this.role = role;
}
}
무분별한 Setter 사용은 객체의 일관성을 유지하기 힘들게 합니다. 때문에 new 생성자(new Member())를 사용할 수 없도록 접근 제어를 PROTECTED로 설정해주었습니다.
객체의 일관성을 유지할 수 있어야 프로그램의 유지 보수성을 끌어 올릴 수 있기 때문에 Setter는 최대한 지양해야 한다고 생각합니다.
@Embeddable
@Getter
public class Address {
private String city; //지역명
private String street; //도로명
private String zipcode; //우편번호
protected Address() {}
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Cart {
@Id
@GeneratedValue
@Column(name = "cart_id")
private Long id;
@OneToOne(fetch = FetchType.LAZY) //모든 ~ToOne 관계는 LAZY로딩으로 설정(EAGER 쓰지 말기!!)
@JoinColumn(name = "member_id")
private Member member;
private Cart(Member member) {
this.member = member;
}
public static Cart createCart(Member member) {
return new Cart(member);
}
}
한명의 회원은 하나의 장바구니를 가질 수 있고 하나의 장바구니는 하나의 회원에만 설정되므로 일대일 관계로 설정하였습니다.
@ManyToOne, @OneToOne 의 관계는 디폴트값이 FetchType.Eager로 되어있습니다. 즉시로딩을 사용하게 되면 연관된 모든 엔티티 테이블에 대하여 조인하기 때문에 예상하지 못한 SQL이 발생합니다.
이로 인해 JPQL에서는 1+N 문제가 발생하게 되고 성능 저하 문제가 발생합니다.
따라서 지연 로딩(FetchType.Lazy)으로 설정하였습니다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item {
@Id
@GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private int price;
private int stockQuantity;
private String description;
@Builder
private Item(String name, int price, int stockQuantity, String description) {
this.name = name;
this.price = price;
this.stockQuantity = stockQuantity;
this.description = description;
}
public static Item createItem(String name, String description, int price, int stockQuantity) {
return new Item(name, description, price, stockQuantity);
}
public void updateItem(String name, String description, int price, int stockQuantity) {
this.name = name;
this.description = description;
this.price = price;
this.stockQuantity = stockQuantity;
}
}
@Entity
@Table(name = "item_image")
@Getter
public class ItemImage {
@Id
@GeneratedValue
@Column(name = "item_image_id")
private Long id;
private String originalName; //원본 파일명
private String storeName; //서버에 저장될 경로명
private String deleteYN; //이미지 파일 삭제 여부
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "item_id")
private Item item;
@Builder
private ItemImage(String originalName, String storeName, String deleteYN) {
this.originalName = originalName;
this.storeName = storeName;
this.deleteYN = "N";
}
public void changeItem(Item item) {
this.item = item;
}
public void deleteSet(String deleteYN) {
this.deleteYN = deleteYN;
}
}
하나의 상품에는 여러 이미지가 등록되기 때문에 다대일의 관계로 설정해주었습니다.
@Entity
@Table(name = "cart_item")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CartItem {
@Id
@GeneratedValue
@Column(name = "cart_item_id")
private Long id;
private int count; //장바구니 아이템 수량
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "cart_id")
private Cart cart;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "item_id")
private Item item;
private CartItem(int count, Cart cart, Item item) {
this.count = count;
this.cart = cart;
this.item = item;
}
public static CartItem createCartItem(int count, Cart cart, Item item) {
return new CartItem(count, cart, item);
}
}
장바구니(Cart)와 상품(Item) : 하나의 장바구니에는 여러 상품이 들어갈 수 있고 하나의 상품은 여러 장바구니에 들어갈 수 있으므로 다대다의 관계가 됩니다. 따라서 Cart_Item 엔티티를 만들어서 일대다(Item:CartItem), 다대일(CartItem, cart)의 관계로 설정했습니다.
@Entity
@Table(name = "orders")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {
@Id
@GeneratedValue
@Column(name = "order_id")
private Long id;
private LocalDateTime orderDate;
@Enumerated(value = EnumType.STRING)
private OrderStatus status;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
@OneToMany(mappedBy = "order")
private List<OrderItem> orderItems = new ArrayList<>();
private Order(OrderStatus status, Member member) {
this.status = status;
this.member = member;
this.orderDate = LocalDateTime.now();
}
//==연관 관계 메서드==//
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.changeOrder(this);
}
public static Order createOrder(OrderStatus status, Member member, OrderItem... orderItems) { //List<OrderItem> list??
Order order = new Order(status, member);
for (OrderItem orderItem : orderItems) {
order.addOrderItem(orderItem);
}
return order;
}
}
최종 주문을 진행하려면 주문 상품이 있어야 하기 때문에 단방향이 아닌 양방향 관계로 설정해주었습니다.
이 때 연관관계의 주인은 ManyToOne에서 Many쪽인 Order를 연관관계의 주인으로 설정해주었습니다.
연관관계의 주인 : 객체와 테이블간에 연관관계를 맺는 차이에서 비롯되었는데 테이블은 외래키, 주키로 조인을 통해 양방향 참조가 가능하지만 객체의 경우 위의 코드와 같이 List를 통해 연결시켜주어야만 양방향이 가능합니다.
즉, 테이블은 외래키 하나로 두 테이블의 연관관계를 관리를 하지만 객체의 경우는 아닙니다.
위의 경우를 보았을 때 OrderItem, Order 둘 중 하나로 외래키를 관리해야 하는데 OrderItem엔티티의 order를 바꿧을 때 외래키(order_id)값을 업데이트 할지 Order엔티티의 orderItems의 값을 변경했을 때 외래키(order_id)값을 업데이트 할지 설정해야 합니다.
이것이 바로 연관관계의 주인으로 연관관계의 주인만이 외래키를 관리할 수 있도록 하고 주인이 아닌쪽은 읽기만 가능하도록 합니다.
//==연관 관계 메서드==//
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.changeOrder(this);
}
양방향 관계의 경우 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정할 수 있도록 연관 관계 메서드를 작성해주었습니다.
@Entity
@Table(name = "order_item")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderItem {
@Id
@GeneratedValue
@Column(name = "order_item_id")
private Long id;
private int count; //주문 수량
private int orderPrice; //주문 총 가격
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "item_id")
private Item item;
private OrderItem(int count, int orderPrice, Item item) {
this.count = count;
this.orderPrice = orderPrice;
this.item = item;
}
public void changeOrder(Order order) {
this.order = order;
}
public static OrderItem createOrderItem(int count, int orderPrice, Item item) {
return new OrderItem(count, orderPrice, item);
}
}