요구사항 분석
- 회원 기능 : 회원 등록, 회원 조회
- 상품 기능 : 상품 등록, 상품 수정, 상품 조회
- 주문 기능 : 상품 주문, 주문 내역 조회, 주문 취소
- 기타 요구 사항 : 상품은 재고 관리가 필요하다. 상품의 종류는 도서, 음반, 영화가 있다. 상품을 카테고리로 구분 할 수 있다. 상품 주문시 배송 정보를 입력할 수 있다.
도메인 모델과 테이블 설계
- 회원과 주문은 1대다 관계이다. 회원 한명당 여러개의 주문을 할 수 있기 때문이다.
- 주문과 배송은 1대1 관계이다. 주문하나당 배송지는 하나이다.
- 주문과 상품은 다대다 관계이다. 한번 주문 할 때, 주문하나당 여러개의 상품을 주문할 수 있고 또 상품도 여러개의 주문에 들어 갈 수있기 때문이다. 다대다 일경우 가운대에 주문상품이라는 테이블을 만들어 1대다 다대1로 풀어야 한다. 주문 상품에는 주문한 물품이들어갈 것 같다.
- 상품은 도서, 음반, 영화로 나뉘고 카테고리와 상품은 다대다관계이다.
회원 엔티티 분석
- 회원(Member) : 이름과 임베디드 타입인 주소(값 타입으로 많이 쓰이는 요소를 Value Type으로 선언해 논 것이다), 그리고 주문 리스트를 가진다. (양방향 관계를 나타내기 위해 사용한다)
- 주문(Order) : Member와 주문한 리스트, Delivery를 외래키로 가진다. 주문한 날짜와, status를 가진다. status는 주문또는 취소 상태로 가지고 있다.
- 주문상품(OrderItem) : 주문 하나당 여러개의 item을 가질 수 있고, 또 가격도 count에 따라 달라지기 때문에 OrderItem 엔티티가 필요하다.
- 배송(Delivery) : 주문시 하나의 배송 정보를 생성한다. 주문과 배송은 1대1 관계이다.
- 주소(Address) : 임베디드 타입으로 여러곳에서 사용한다.
- 아이템(Item) : 각 item을 나타 낼 수 있는 엔티티로 Category 정보와 상속 관계로 Album, Book, Movie를 가지고 있다.
- 카테고리(Category) : 카테고리는 부모와 아이의 관계로 대칭 구조이다.
회원 테이블 분석
ITEM은 싱글테이블 전략을 사용했다. MEMBER와 DELIVERY에는 ADDRESS가 존재한다.
카테고리는 엔티티에서는 아이템이 카테고리를 가질 수 있고 카테고리가 아이템을 가질 수 있지만 테이블에서는 그럴 수 없기 때문에 1대다 다대1 관계로 풀어야 한다.
- 주인 선정 : 기본편에서 배운대로 주인은 1대다 관계중 다를 주인으로 설정한다. 왜냐하면 다에 외래키가 존재하기 때문이다. 그러므로 Orders.member는 주인 Member에 OrderList는 mappedBy로 설정한다.
- 일대일 관계는 외래키를 둘중에 아무한테 둘 수 있으므로 예제에서는 ORDERS에 DELIVERY_ID를 설정했으므로 ORDERS.DERIVERY_ID 가 주인이다.
엔티티 클래스 개발
실무에서는 Getter는 열어두고 Setter는 꼭 필요한 경우에만 사용하는 것을 추천한다.
- Member
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String name;
@Embedded
private Address address;
@OneToMany(mappedBy = "member")//Order와 일대다 관계 설정
private List<Order> orders = new ArrayList<>();
}
- Address
@Embeddable
@Getter //Setter는 설정해주면 안된다. 변경되면 안된다.
public class Address {
private String city;
private String street;
private String zipcode;
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
protected Address(){ //사용금지
}
}
- Order
@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {
@Id @GeneratedValue
@Column(name = "orders_id")
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL) //양방향 관계 설정
private List<OrderItem> orderItems = new ArrayList<>();
//cascade는 OrderItem을 일일이 persist 안하고 Order만 persist해도 따라 persist할 수 있다.
@OneToOne
@JoinColumn(name = "delivery_id", cascade = CascadeType.ALL) //주인 설정
private Delivery delivery;
private LocalDateTime orderDate;
@Enumerated(EnumType.STRING) //enum String으로 지정
private OrderStatus status;
//==연관관계 메서드==//
//연관관계 메서드는 양방향 관계일 때 만들어주고 먼가 주도하는 쪽에 만든다.
public void setMember(Member member){
this.member = member;
member.getOrders().add(this);
}
public void addOrderItem(OrderItem orderItem){
orderItems.add(orderItem);
orderItem.setOrder(this);
}
public void setDelivery(Delivery delivery){
this.delivery = delivery;
delivery.setOrder(this);
}
}
- OrderItem
@Entity
@Getter @Setter
public class OrderItem {
@Id @GeneratedValue
@Column(name = "order_item_id")
private Long id;
@ManyToOne
@JoinColumn(name = "item_id")
private Item item;
@ManyToOne
@JoinColumn(name = "order_id")
private Order order;
private int orderPrice;
private int count;
}
- Delivery
@Entity
@Getter @Setter
public class Delivery {
@Id @GeneratedValue
@Column(name = "delivery_id")
private Long id;
@OneToOne(mappedBy = "delivery")
private Order order;
@Embedded
private Address address;
@Enumerated(EnumType.STRING)
private DeliveryStatus status;
}
- Item
@Entity
@Getter @Setter
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) //sing_table로 상속관계 모두 생성
@DiscriminatorColumn(name = "dtype")
public abstract class Item {
@Id
@GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private int price;
private int stockQuantity;
@ManyToMany(mappedBy = "items") // ManyToMany로 카테고리와 설정
private List<Category> categories = new ArrayList<>();
}
- Category
@Entity
@Getter @Setter
public class Category {
@Id @GeneratedValue
@Column(name = "category_id")
private Long id;
private String name;
@ManyToMany
@JoinTable(name = "category_item",
joinColumns = @JoinColumn(name = "category_id"),
inverseJoinColumns = @JoinColumn(name ="item_id")
) //실무에서 거의 못씀
private List<Item> items = new ArrayList<>();
@ManyToOne
@JoinColumn(name = "parent_id") //부모
private Category parent;
@OneToMany(mappedBy = "parent") //자식
private List<Category> child = new ArrayList<>();
//==연관관계 편의 매서드==//
public void addChildCategory(Category child){
this.child.add(child);
child.setParent(this);
}
}
실행 결과
엔티티 설계시 주의점
- 엔티티에는 가급적 Setter를 사용하지 말자!!
변경 포인트가 너무 많아서, 유지보수가 어렵다.
- 모든 연관관계는 지연로딩으로 설정한다.
즉시로딩은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기가 어렵다. 특히 JPQL을 실행할 때 N+1문제가 자주 발생한다.
XToOne은 디폴트가 즉시로딩이다. 그러므로 LAZY로 변경해줘야 한다. 기본편에서 배운 fetch join또는 엔티티 그래프 기능이 즉시 로딩처럼 내용을 즉시 가져온다. 이걸로 최적화를 하면된다.
ctrl-shift-f로 ManyToOne과 OneToOne을 찾아 (fetch = FetchType.LAZY)를 추가해주어야 한다.
- 컬렉션은 필드에서 초기화 하자.
컬렉션은 필드에서 초기화 하는 것이 안전하다. null 문제에서 안전해진다. 컬렉션은 최대한 변경하면 안된다.
Member member = new Member();
System.out.println(member.getOrders().getClass());
em.persist(member);
System.out.println(member.getOrders().getClass());
//출력 결과
//class java.util.ArrayList
//class org.hibernate.collection.internal.PersistentBag
제일 처음 Member를 만들 때는 ArrayList로 잘 초기화 되지만 영속성 컨텍스트에 넣는 순간 하이버네이트가 제공하는 내장 컬렉션으로 변경된다. 이 상황에서 ArrayList를 잘 못 set할 경우 하이버네이트 내부 메커니즘에 문제가 발생할 수 있다. 그러므로 컬렉션 필드는 절대 변경하지 않는다.
- 테이블, 칼럼명 생성 전략
하이버네이트 기존 구현은 엔티티의 필드명을 그대로 테이블의 컬럼명으로 사용한다.
스프링부트는 ( SpringPhysicalNamingStrategy )을 사용한다.
스프링 부트 신규 설정 (엔티티(필드) 테이블(컬럼))
- 카멜 케이스 -> 언더스코어(memberPoint -> member_point)
- .(점) -> _(언더스코어)
- 대문자 -> 소문자
논리명 생성 : 명시적으로 컬럼, 테이블명을 직접 적지 않으면 ImplicitNamingStrategy 사용
spring.jpa.hibernate.naming.implicit-strategy : 테이블이나, 컬럼명을 명시하지 않을 때 논리명 적용
물리명 적용 : spring.jpa.hibernate.naming.physical-strategy : 모든 논리명에 적용됨, 실제 테이블에 적용
강의 출저
실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런 | 강의
실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다., - 강
www.inflearn.com
'BackEnd > 실전! 스프링 부트와 JPA 활용1' 카테고리의 다른 글
웹 계층 개발(회원) (0) | 2023.01.06 |
---|---|
주문 도메인 개발 (0) | 2023.01.05 |
상품 도메인 개발 (0) | 2023.01.04 |
회원 도메인 개발 (0) | 2023.01.04 |
프로젝트 환경설정 (0) | 2023.01.02 |