요구사항 분석
회원 기능
● 회원가입
- 이름
- 도시
- 주소
- 우편 번호
● 회원 목록
순서, 이름, 도시, 주소, 우편번호 출력
상품 기능
● 상품 등록
- 상품명
- 가격
- 수량
- 저자
- ISBN
● 상품 목록
- 순서, 상품명, 가격, 재고 수량 출력
- 수정 기능 포함
주문 기능
● 상품 주문
- 주문 회원(이름)
-상품명
-주문 수량
● 주문 내역
- 순서, 회원 명, 대표 상품 이름, 대표 상품 주문 가격, 대표 상품 주문 수량, 상태(ORDER/CANCLE), 일시 출력
- 취소 기능 포함(취소 시 상품 목록에 다시 상품이 추가됨)
기타
모든 섹션에는 회원명을 기준으로 한 검색 기능이 있다.
도메인 모델과 테이블 설계
회원 : 주문 - 하나의 회원 당 여러개의 주문
주문 : 배송 - 하나의 주문 당 하나의 배송
주문 : 주문 상품 - 하나의 주문 당 여러 개의 주문 상품
CATEGORY_ITEM 테이블로 1 대 다 연관관계를 풀었다.
테이블 설계 어렵다 .. ㅠㅠ
연관 관계 매핑 분석
회원과 주문 : 일 대 다, 다 대 다 양방향 관계 --> 주문 테이블에서 외래 키를 두고, 연관 관계의 주인으로 설정
주문 상품과 상품 : 주문 상품에는 상품의 번호를 알 수 있는 FK가 존재. 주문 상품이 연관관계의 주인
카테고리와 상품 : @ManyToMany 어노테이션을 사용해서 매핑(다대다 관계를 보여주기 위하여)
엔티티 클래스 개발
회원을 관리하는 Member Entity를 개발해보자.
package jpabook.jpashop.domain;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "User")
@Getter @Setter // lombok으로 Getter Setter를 모두 연다!
public class Member {
@Id @GeneratedValue // PK로 지정, Sequence 값을 사용
@Column(name = "member_id")
private Long id;
private String name; // 회원명
@Embedded // 내장 타입임을 명시 ?? 내장 타입이라는 것은 무슨 얘기지
private Address address;
@OneToMany(mappedBy = "member", fetch = FetchType.LAZY)
// Member의 입장에서, Order은 일대다 관계(연관관계 표시)
// Order table의 Member field에 의해 매핑됨(Mapped). 연관관계의 주인은 Member가 아닌 Order
private List<Order> orders = new ArrayList<>();
public void setUsername(String memberA) {
name = memberA;
}
public String getUsername() {
return name;
}
}
Member 클래스의 멤버 변수는 id, name, address, orders이다.
@Column으로 테이블명을 지정하고, member 테이블로 mapping 한다.
@Embedded 어노테이션 : 내장 타입임을 명시하는 것이다. 즉 Address 클래스를 따로 설계해서 내부에 City, Street, ZipCode와 같은 정보들을 포함하여 멤버 변수를 관리하고 있음을 알리는 것이다.
// jpa의 내장 타입이라는 것을 명시
@Embeddable
@Getter @Setter
public class Address{
private String city;
private String street;
private String zipcode;
// 기본 생성자 생성(Setter를 쓰면 값이 변경 가능해지므로 생성자로 값을 지정)
protected Address(){
} // public보다 protected가 더 안전
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
}
@OneToMany 와 @ManyToOne의 사용
package jpabook.jpashop.domain;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "orders") // 테이블 명을 지정
@Getter @Setter
public class Order {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
// ManyToOne은 fetch의 default가 EAGER
@ManyToOne(fetch = FetchType.LAZY) // order의 입장에서, Member은 다대일 관계
@JoinColumn(name = "member_id") // FK가 member_id로 지정
private Member member;
//JPQL select o From order o; --> SQL select * from order
// OneToMany는 fetch의 default가 Lazy, 굳이 추가하지 않아도 된다.
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "delivery_id") // Order이 Delivery와의 연관관계의 주인이 된다.
private Delivery delivery;
private LocalDateTime orderDate; // 주문 시간, hibernate에서 지원하는 LocalDateTIme
@Enumerated(EnumType.STRING)
private OrderStatus status; // 주문 상태 [Order, Cancle]
// 연관관계 메서드
public void setMember(Member member) {
this.member =member;
}
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.setOrder(this);
}
public void setDelivery(Delivery delivery) {
this.delivery = delivery;
delivery.setOrder(this);
}
}
Member과 Order 테이블을 통해 양방향 연관관계 매핑에 대해 알아보자.
Member과 Order은 일대다 관계이고, FK관계는 Order에 표시되어 있다.( 그래서 Order 클래스가 '연관관계의 주인'이라고 칭한다. 연관 관계의 주인이라는 것의 정확한 개념은?)
https://victorydntmd.tistory.com/208
먼저 Member_ID를 보자.
@ManyToOne인 이유는 Order : Member = 다 : 일 인데, 현재 Order클래스에서 어노테이션을 추가한 상황이므로 @ManyToOne을 사용한 모양이다!
또한 Member클래스의 PK인 member_id를 @JoinColumn에 추가해 FK관계를 설정해주었다.
반대로 Member 입장에서는 다대일이 아닌 일대다(Member : Order) 이기 때문에, @OneToMany 어노테이션을 사용하였다.
mappeBy 속성을 사용하는 것은 연관관계의 주인이 아닌 클래스, Member가 되는 것이고 아래에 나오는 orders 필드가 Member에 의해 매핑되므로 mappedBy로 연관관계의 주인이 Member가 아닌 Order라는 것을 JPA에게 알린다.
다음으로 Order과 Delivery 클래스의 연관관계 매핑을 알아보자.
Order과 Delivery는 일대일 관계
@OneToOne 어노테이션을 사용해 매핑한다.
package jpabook.jpashop.domain;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter @Setter
public class Delivery {
@Id @GeneratedValue
@Column(name = "delivery_id")
private Long id;
@OneToOne(mappedBy = "delivery",fetch = FetchType.LAZY)
private Order order;
@Embedded
private Address address;
@Enumerated(EnumType.STRING)
// ORDINARY를 쓰면 EnumType을 사용하는 과정에서 중간에 다른 상태가 추가되면 순서가 밀리게 된다.
// 즉 STRING 구분을 사용해 Enum을 정의한다.
private DeliveryStatus status; // READY(배송 전), COMP(배송 중)
}
DeliveryStauts는 Enum타입으로 정의하였다.
package jpabook.jpashop.domain;
public enum DeliveryStatus {
READY, COMP
}
@Enumerated 란?
EnumType을 엔티티 클래스의 속성으로 사용함을 JPA에게 알리는 어노테이션이다.
https://lng1982.tistory.com/280
이외에 Order클래스에 필요한 orderDate와 Status관련 필드까지 추가해주었다.
package jpabook.jpashop.domain;
public enum OrderStatus {
ORDER, CANCLE
}
연관관계 필드를 추가했으면 set함수를 이용해서 연관관계 메서드를 만들어주어야한다.
OrderItem은 List이므로 add함수와 setOrder로 연관관계를 구현한다.
package jpabook.jpashop.domain;
import jpabook.jpashop.domain.item.Item;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter @Setter
public class OrderItem {
@Id @GeneratedValue
@Column(name = "order_item_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY) // OrderItem 입장에서 Item 은 다대일
@JoinColumn(name = "item_id")
private Item item;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
private int orderPrice; // 주문 당시의 가격
private int count; // 주문 당시의 수량
}
setOrder 메소드를 따로 구현하지 않았음에도 사용이 가능한 이유는 여기 OrderItem 클래스에 @Getter @Setter 어노테이션을 추가해주었기 때문이다.
Order과 OrderItem은 일대다 관계이므로 OrderItem 클래스에서 @ManyToOne 어노테이션을 추가하고 Order 테이블의 PK인 order_id를 속성으로 넣어준 것을 확인할 수 있다.
Order클래스에는 반대로 @OneToMany 가 추가되었고, 역시 mappedBy 속성을 사용해 orderItem 클래스가 연관관계의 주인임을 알려주었다.
그리고 일대다 관계 (하나의 주문에 여러개의 주문품목)인 상황이므로, order클래스에 orderItem 필드를 추가해줄 때 List 형태로 new연산자를 사용해 OrderItem 열거형을 만들어준다.
이에 따라 위에서 연관관계 메소드를 만들때 단순히
public void setOrderItem(OrderItem orderItem)
{
this.orderItem = orderItem;
}
이 아닌
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.setOrder(this);
}
다음과 같이 구현했음을 알 수 있다. (List에 먼저 추가해주고 Order 연관관계를 설정)
이제 OrderItem과 Item의 연관관계를 설정해주자!
OrderItem : Item = 다 : 일 관계이고 연관관계의 주인은 OrderItem이다.
따라서 @ManyToOne .. 왜 OrderItem과 Item이 다대일 관계인지 모르겠다 ㅠ
아무튼 JoinColulmn으로 item_id로 FK를 지정해주고
package jpabook.jpashop.domain.item;
import jpabook.jpashop.domain.Category;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter
@Setter
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")
private List<Category> categories = new ArrayList<Category>();
}
Item 클래스는 추상으로 만들어주어 구현체는 따로 만들어준다.
기본적인 id, name, price, stockQuantity를 지정해주고 나머지 요소들은 @ManyToMany를 사용해 Category클래스간의 다대다 연관관계를 지정해준다.
package jpabook.jpashop.domain;
import jpabook.jpashop.domain.item.Item;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter @Setter
public class Category {
@Id @GeneratedValue
@Column(name = "category_id")
private Long id;
private String name;
// 모든 코드를 지연 로딩으로 설정하기.
@ManyToMany(fetch = FetchType.LAZY) // 카테고리와 리스트는 다대다 관계
@JoinTable(name = "category_item",
joinColumns = @JoinColumn(name = "category_id"),
inverseJoinColumns = @JoinColumn(name = "item_id"))
private List<Item> items = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_id") // 연관관계 표현
private Category parent;
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY) // mapped by에 parent를 넣어줌으로써 FK를 지정?
private List <Category> child = new ArrayList<>();
// 연관관계 메서드
public void addChildCategory(Category child) {
this.child.add(child); // 자식이 추가되고
child.setParent(this); // 자식도 부모에 대한 정보를 얻는다.
}
}
Category 클래스와 Item 클래스의 FK를 설정해 준 이 코든는 이해가 가는데,
child객체를 만들어준 이 부분은 이해가 잘 가지 않는다.
Category자료형 List와 Category 클래스 간의 @ManyToOne, @OneToMany 연관관계를 매핑한 것인가??
엔티티 설계시 주의할 점
1. 엔티티는 가급적 Setter를 사용하지 말자.
2. 모든 연관관계는 지연(Lazy)로딩으로 설정하기.
3. 컬렉션은 필드에서 초기화하기!
ex) Member member = new Member();
하이버네이트는 엔티티를 영속화할 때, 컬렉션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경된다.
컬렉션을 가급적 꺼내지 말고 처음에 객체를 생성할 때 초기화한다.
4. 테이블, 컬럼명 생성 전략
엔티티 필드(객체)는 카멜케이스를 언더 스타일로 변경한다.
@Table(name = "")을 따로 지정하지 않으면 클래스 명으로 테이블 명을 지정
(하이버네이트 기존 구현)
'Framework > Spring' 카테고리의 다른 글
스프링 부트 JPA 활용 - 웹 계층 개발 (0) | 2021.07.07 |
---|---|
스프링 부트 JPA 활용 1 - 회원 도메인 개발 (0) | 2021.06.23 |
[스프링 입문] 스프링 빈과 의존 관계 (0) | 2021.05.25 |
스프링 부트와 JPA 활용1 - 프로젝트 환경 설정 (0) | 2021.05.19 |
스프링 관련 의문점 다루기 (0) | 2021.02.06 |