목차
- 요구사항 분석
- 도메인 모델과 테이블 설계
- 엔티티 클래스 개발1
- 엔티티 클래스 개발2
- 엔티티 설계시 주의점
요구사항 분석
간단한 쇼핑몰을 만들것이다.
기능 목록
회원 기능 | - 회원 등록 - 회원 조회 |
상품 기능 | - 상품 등록 - 상품 수정 - 상품 조회 |
주문 기능 | - 상품 주문 - 주문 내역 조회 - 주문 취소 |
기타 요구사항 | - 상품은 재고 관리가 필요하다. - 상품의 종류는 도서, 음반, 영화가 있다. - 상품을 카테고리로 구분할 수 있다. - 상품 주문시 배송 정보를 입력할 수 있다. |
도메인 모델과 테이블 설계
- 회원, 주문, 상품의 관계
- 회원은 여러 상품을 주문할 수 있다.
- 한 번 주문할 때 여러 상품을 선택할 수 있으므로 주문과 상품은 다대다 관계다.
- 하지만, 이런 다대다 관계는 관계형 데이터베이스는 물론이고, 엔티티에서도 거의 사용하지 않는다.
-> 운영에선 사용하면 안 된다! 기본편에 설명해주셨다 한다. - 다라서 그림처럼 엔티티를 추가해 다대다 관계를 일대다, 다대일 관계로 풀어냈다.
- 상품 분류
- 상품은 도서, 음반, 영화로 구분되는데, 상품이라는 공통 속성을 사용하므로, 상속 구조로 표현했다.
회원 엔티티 분석
- 주문(Order)의 주문 상태(status)는 열거형으로 주문(ORDER), 취소(CANCEL)을 표현할 수 있다.
- 상품(Item)의 종류는 도서, 음반, 영화가 있는데, 각각은 사용하는 속성이 다르다.
- 카테고리(Category)는 계층구조로 parent, child로 부모, 자식 카테고리를 연결한다.
- 참고
실무에선 회원이 주문을 참조하지 않고, 주문이 회원을 참조하는 것으로 충분하다.
여기선 일대다, 다대다 양방향 연관관계를 설명하기 위해 추가했다.
member랑 order를 동급으로 생각해라. 주문을 생성할 때 회원이 필요하다고 생각해라.
조회도 주문내역이 필요하면, order에서 필터링 조건에 member가 들어가는 식으루
회원 테이블 분석
- 관계형 데이터베이스는 다대다 관계가 안 된다. 따라서 카테고리처럼 중간에 매핑 테이블을 둔다.
- 회원 엔티티의 Address 임베디드 타입 정보가 회원 테이블에 그대로 들어갔다.
+ Delivery도 마찬가지다. - ITEM은 앨범, 도서, 영화 타입을 통합해 하나의 테이블로 만들었다.
-> DTYPE 칼럼으로 타입 구분 - ODERS인 것은, 데이터베이스가 order by를 예약어로 잡고 있는 경우가 많아 관례상 사용한다.
연관 관계 매핑 분석
- 회원과 주문
- 일대다, 다대일의 양방향 관계다.
- 따라서 연관관계의 주인을 정해야하는데, 외래키가 있는 주문을 연관관계 주인으로 정하는 것이 좋다.
- 그러므로 Order.member를 ORDERS.MEMBER_ID 외래키가 매핑한다.
- 주문상품과 주문
- 다대일 양방향 관계다.
- 외래키가 주문상품에 있으므로, 주문상품이 연관관계의 주인이다.
- 그러므로 OrderItem.order를 ORDER_ITEM.ORDER_ID 외래키와 매핑한다.
- 주문 상품과 상품
- 다대일 단방향 관계다.
- OrderItem.item을 ORDER_ITEM.ITEM_ID 외래키와 매핑한다.
- 주문과 배송
- 일대일 양방향 관계다.
- Order.delivery를 ORDERS.DELIVERY_ID 외래키와 매핑한다.
- 카테고리와 상품
- @ManyToMany를 사용하기 위해서 매핑한다.
- (실무에선 @ManyToMany는 사용하지 말자. 여기서 다대다 관계를 예제로 보여주려 추가한것일 뿐이다.)
엔티티 클래스 개발1
- 실무에선 가급적 Getter는 열어두고, Setter는 꼭 필요한 경우에만 사용하는 것을 추천한다.
회원 엔티티 / 주문 엔티티
- @Embeddable : 어딘가 내장될 수 있다.
- @Embedded : 내장타입이다.
- @ManyToOne / @OneToMany
- @JoinColumn
- Member와 Order는 양방향 관계다.
- 9:30
- 멤버는 Orders를 리스트로 가지고 있고, 오더도 멤버를 가지고 있다. -> 양방향 참조
- 그럼 JPA는 어디서 값을 확인해야 하는가? 혼란이 온다.
- JPA에선 둘 중 하나만 하도록 약속했는데 ,연관관계 주인이 있는 곳이다.
- 연관관계 주인은 FK가 가까운 곳으로 한다. 이 경우 Order의 member다.
주문상품 엔티티
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
- 상속관계 전략
- SINGLE_TABLE : 한 테이블에 모두 하겠다. -> 테이블 분석에 ITEM 테이블
@Enumerated(EnumType.STRING)
- EnumType.ORDINAL 로 하게되면 숫자로 정의되는데, 중간에 값을 투입하면, 처음과 정의가 달라진다.
- 따라서 STRING으로 설정해야 한다.
Order와 Delivery
- 1대 1 관계이므로, FK를 어디에 둬도 상관없다.
- 강사님은 주로 Access를 많이 하는 곳에 둔다.
- 따라서 연관관계 주인은 FK가 가까운 Order에 잡으면 된다.
엔티티 클래스 개발2
카테고리 엔티티
- 실무에선 @ManyToMany를 사용하지 말자.
편리한 것 같지만, 중간 테이블에 컬럼을 추가할 수 없고, 세밀하게 쿼리를 실행하기 어렵다.
따라서 실무에서 사용하기에는 한계가 있다. - 중간 엔티티를 만들고, @ManyToOne, @OneToMany로 매핑해서 사용하자.
정리하면 다대다 매핑을 일대다, 다대일 매핑으로 풀어내서 사용하자.
- 테이블 생성이 잘 되었다.
- ++ FK는 시스템에 따라 선택이다.
- 실시간 트래픽이 중요하다면, 정합성보다 잘 서비스되는 것이 더 중요하다면, FK 빼고 인덱스만 잘 잡아주면 된다.
- 금융처럼 중요하고, 데이터가 맞아야 된다면, FK를 진지하게 고민해야 한다.
주소 값 타입
- 값 타입은 변경 불가능하게 설계해야한다.
- 따라서 @Setter 제거하고, 생성자에서 값을 모두 초기화해서 변경 불가능한 클래스를 만든다.
- JPA 스펙상 엔티티나 임베디드 타입은 자바 기본 생성자(default constructor)를 public 또는 protected로 설정해야한다.
- public으로 두는 것 보단, protected로 설정하는 것이 그나마 더 안전하다.
- JPA가 이런 제약(기본 생성자가 있어야 한다.)을 두는 이유는, JPA 구현 라이브러리가 객체를 생성할 때 리플렉션 같은 기술을 사용할 수 있도록 지원해야 하기 때문니다.
엔티티 설계시 주의점
엔티티에는 가급적 Setter를 사용하지 말자.
- Setter가 모두 열려있다. 변경 포인트가 너무 많아서, 유지보수가 어렵다.
*** 모든 연관관계는 지연로딩으로 설정해야 한다.
- 즉시로딩( EAGER )은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어렵다.
- 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다.
-> 100개를 조회하기 위한 1개의 쿼리 + 100개의 조회 쿼
- 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다.
- 연관된 엔티티를 함께 DB에서 조회해야 하면, fetch join 또는 엔티티 그래프 기능을 사용한다.
- @XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이므로, 직접 지연로딩으로 설정해야 한다.
컬렉션은 필드에서 초기화 하자.
- 컬렉션은 필드에서 바로 초기화 하는 것이 안전하다.
- null 문제에서 안전하다.
- 하이버네이트는 엔티티를 영속화 할 때, 컬랙션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경한다.
- 만약 getOrders() 처럼 임의의 메서드에서 컬력션을 잘못 생성하면, 하이버네이트 내부 메커니즘에 문제가 발생할 수 있다.
// 이렇게 하지 말고,
private List<Order> orders;
public Member() {
orders = new ArrayList<>();
}
// 이렇게 해라.
private List<Order> orders = new ArrayList<>();
// null 문제에서 안전해진다.
// 초기화 신경쓸 필요가 없다.
테이블, 컬럼명 생성 전략
- 스프링 부트에서 하이버네이트 기본 매핑 전략을 변경해서 실제 테이블 필드명은 다름
스프링 부트 신규 설정 (엔티티(필드) -> 테이블(컬럼))
- 카멜 케이스 -> _
- . -> _
- 대문자 -> 소문자
casecade
- persist를 각각 할 필요없이 하나만 해주면, 하위 엔티티로 모든 작업을 전파한다.
- Order에 persist를 해주면, delivery와 orderItems 모두 적용된다.
연관관계 (편의) 메서드
- 양방향 연관관계에서 양쪽 모두 관계를 맺어줄 때 사용한다.
- 아래 코드와 같이 한 번에 양방향 관계를 설정하는 메서드다.
- 코드를 간결하게 구현할 수 있고, 원자적으로 묶을 수 있어 안전하다.
//==연관관계 (편의) 메서드==//
public void addChildCategory(Category child) {
this.child.add(child);
child.setParent(this);
}
// origin
public static void main(String args[]) {
Member member = Member();
Order order = new Order();
member.getOrders().add(order);
order.setMember(member);
}
참고자료
'프로그래밍 > Spring' 카테고리의 다른 글
[Spring boot / JPA] 4. 회원 도메인 개발 (0) | 2023.07.01 |
---|---|
[Spring boot / JPA] 3. 애플리케이션 구현 준비 (0) | 2023.06.30 |
[Spring boot / JPA] 1. 프로젝트 환경설정 (0) | 2023.06.28 |
[Spring Boot] 9. 빈 스코프 (0) | 2023.06.13 |
[Spring boot] 8. 빈 생명주기 콜백 (0) | 2023.06.11 |