요구사항 분석과 기본 매핑
- 작은 쇼핑몰을 만들어가면서 JPA로 실제 도메인 모델을 어떻게 구성하고 객체와 테이블을 어떻게 매핑해야 하는지 알아보자
요구사항 분석
- 핵심 요구사항
- 회원은 상품을 주문할 수 있다.
- 주문 시 여러 종류의 상품을 선택할 수 있다.
- 회원 기능
- 회원 등록
- 회원 조회
- 상품 기능
- 상품 등록
- 상품 수정
- 상품 조회
- 주문 기능
- 상품 주문
- 주문 내역 조회
- 주문 취소
도메인 모델 분석
- 회원, 주문, 상품, 그리고 주문상품이라는 엔티티가 도출
- 회원과 주문의 관계 :
회원은 여러 번 주문할 수 있으므로 회원과 주문은 일대다 관계 - 주문과 상품의 관계 :
주문을 할 때 여러 상품을 함께 선택할 수 있고, 같은 상품도 여러 번 주문될 수 있으므로 다대다 관계
하지만 다대다 관계는 거의 사용하지 않으므로 주문상품이라는 연결 엔티티를 추가해 다대다 관계를 일대다, 다대일 관계로 변경
테이블 설계
- 요구사항을 기반으로 설계된 테이블
- 회원 : 이름과 주소를 가지며, 주소는 CITY, STREET, ZIPCODE로 표현
- 주문 : 상품을 주문한 회원을 외래키로 가지며, 주문 날짜(ORDERDATE)와 주문 상태(STATUS)를 가짐
- 주문상품 : 주문과 주문한 상품을 외래 키로 가지며 주문 금액과 주문 수량 정보를 가짐
- 상품 : 이름, 가격, 재고수량을 가지며 상품을 주문하면 재고수량이 줄어듦
엔티티 설계와 매핑
- 이 설계한 테이블을 기반으로 엔티티 생성
- 회원 엔티티
// Member.java
@Entity // 회원 엔티티
public class Member {
/* 식별자 자동 생성, 기본 생성 전략인 AUTO이므로
H2 데이터베이스에서는 SEQUENCE 사용 */
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String name; // 이름
private String city; // 주소 CITY, STREET, ZIPCODE
private String street;
private String zipcode;
//Getter, Setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getZipcode() {
return zipcode;
}
public void setZipcode(String zipcode) {
this.zipcode = zipcode;
}
}
-
- 주문 엔티티
// Order.java
@Entity // 주문 엔티티
@Table(name = "ORDERS")
public class Order {
@Id
@GeneratedValue
@Column(name = "ORDER_ID")
private Long id;
@Column(name = "MEMBER_ID")
private Long memberId; // 상품을 주문할 회원의 외래키 값
/* 주문 날짜는 Date를 사용하고 년월일 시분초를 모두 사용하므로
@Temporal에 TemporalType.TIMESTAMP 속성 사용 */
@Temporal(TemporalType.TIMESTAMP)
private Date orderDate; // 주문 날짜
/* 열거형을 사용하므로 @Enumerated로 매핑하고
EnumType.STRING 속성을 지정해 열거형의 이름을 그대로 저장 (ORDER, CANCEL) */
@Enumerated(EnumType.STRING)
private OrderStatus status; // 주문 상태
//Getter, Setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getMemberId() {
return memberId;
}
public void setMemberId(Long memberId) {
this.memberId = memberId;
}
public Date getOrderDate() {
return orderDate;
}
public void setOrderDate(Date orderDate) {
this.orderDate = orderDate;
}
public OrderStatus getStatus() {
return status;
}
public void setStatus(OrderStatus status) {
this.status = status;
}
}
// OrderStatus.java
public enum OrderStatus { // 주문 상태
ORDER, CANCEL
}
-
- 주문상품 엔티티
// OrderItem.java
@Entity // 주문상품 엔티티
@Table(name = "ORDER_ITEM")
public class OrderItem {
@Id
@GeneratedValue
@Column(name = "ORDER_ITEM_ID")
private Long id;
@Column(name = "ITEM_ID")
private Long itemId; // 주문한 상품의 외래 키 값
@Column(name = "ORDER_ID")
private Long orderId; // 주문의 외래 키 값
private int orderPrice; // 주문 금액
private int count; // 주문 수량
//Getter, Setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getItemId() {
return itemId;
}
public void setItemId(Long itemId) {
this.itemId = itemId;
}
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public int getOrderPrice() {
return orderPrice;
}
public void setOrderPrice(int orderPrice) {
this.orderPrice = orderPrice;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public String toString() {
return "OrderItem{" +
"id=" + id +
", buyPrice=" + orderPrice +
", count=" + count +
'}';
}
}
-
- 상품 엔티티
// Item.java
@Entity // 상품 엔티티
public class Item {
@Id @GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name; // 이름
private int price; // 가격
private int stockQuantity; // 재고수량
//Getter, Setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getStockQuantity() {
return stockQuantity;
}
public void setStockQuantity(int stockQuantity) {
this.stockQuantity = stockQuantity;
}
@Override
public String toString() {
return "Item{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
'}';
}
}
데이터 중심 설계의 문제점
- 관계형 데이터베이스는 연관된 객체를 찾을 때 외래 키를 사용해서 조인하면 되지만
객체는 조인이라는 기능이 없으며 연관된 객체를 찾을 때 참조를 사용해야 함 - 위 방식의 경우 객체 설계를 테이블 설계에 맞춘 방법으로 테이블의 외래 키를 객체에 그대로 가져왔음
그러므로 객체에서 참조 대신 데이터베이스의 외래 키를 그대로 가지고 있으므로 객체 그래프를 탐색할 수 없음 - 그러므로 외래 키만 가지고 있으면 연관된 엔티티를 찾을 때 외래 키로 데이터베이스를 다시 조회해야하는 문제점이 발생
// 주문을 조회한 다음 주문과 연관된 회원을 외래 키를 가지고 조회하기
Order order = em.find(Order.class, orderId);
// 외래 키로 다시 조회
Member member = em.find(Member.class, order.getMemberId());
- 위와 반대로 객체에서 참조를 사용해서 연관관계를 조회할 수 있음
// 참조를 사용한 객체지향적인 방법
Order order = em.find(Order.class, orderId);
Member member = order.getMember(); // 참조 사용
- 객체와 테이블 사이에는 큰 차이가 있으므로 JPA를 사용해 객체의 참조와 테이블의 외래 키를 매핑해서
객체에서는 참조를 사용하고 테이블에서는 외래 키를 사용할 수 있도록 해야함
'Java-Spring > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 연관관계 매핑 기초 - 실전 예제 (0) | 2022.03.31 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 연관관계 매핑 기초 (0) | 2022.03.29 |
[자바 ORM 표준 JPA 프로그래밍] 엔티티 매핑 (0) | 2022.03.23 |
[자바 ORM 표준 JPA 프로그래밍] 영속성 관리 (0) | 2022.03.20 |
[자바 ORM 표준 JPA 프로그래밍] JPA 시작 (0) | 2022.03.15 |