학습했던 JPA 내용에 대해 복습하고 실제 코드로 적용해보는 용도의 간단한 쇼핑 프로젝트입니다.
기능
- 회원 기능
- 회원 등록
- 회원 조회
- 상품 기능
- 상품 등록
- 상품 수정
- 상품 조회
- 주문 기능
- 상품 주문
- 주문 내역 조회
- 주문 취소
- 기타 요구 사항
- 상품은 재고 관리가 필요하다.
- 상품의 종류는 도서, 음반, 영화가 있다.
- 상품을 카테고리로 구분 가능하다.
- 상품 주문 시 배송 정보를 입력할 수 있다.
회원 엔티티
- Member
- 이름
- 주소 (임베디드 타입)
- 주문 (리스트)
- Order
- 한 번 주문 시 여러 상품을 구매 가능하다. 따라서 Order과 OrderItem은 일대다 관계를 가진다.
- 주문 회원, 주문 날짜
- 주문 상태 (Enum)
- OrderItem
- 주문 금액
- 주문 수량
- Item
- 이름, 가격
- 재고 수량 : 상품 주문 시 재고 수량이 줄어든다.
- 카테고리
- Delivery
- 주문 시 하나의 배송 정보를 생성한다.
- Order와 일대일 관계를 가진다.
- Category
- Item과 다대다 관계를 갖는다.
- 상속으로 부모, 자식 카테고리를 연결한다.
- Address
- 임베디드 타입으로 Member와 Delivery에 사용된다.
설계에서는 회원이 주문 리스트를 가지고 있다.
회원이 주문 리스트를 가지고 있는 것이 회원이 주문을 하기 때문에 옳게 보이지만, 실무에서는 보통 회원이 주문을 참조하지 않고 주문이 회원을 참조하는 것으로 충분하기 때문에 사용하지 않는다.
학습용 프로젝트이기 때문에 일대다, 다대일의 양방향 연관관계를 사용해보기 위한 설정이다.
회원 테이블
- Member, Delivery 엔티티의 Address 임베디드 타입 정보가 회원 테이블에 그대로 들어간다. (City, Street, ZipCode)
- Item의 카테고리인 Album, Book, Movie 타입의 컬럼이 통합되어 하나의 테이블이 되었다.
- DTYPE으로 카테고리가 구분된다.
연관관계 매핑
- Member - Order
- 일대다, 다대일의 양방향 관계이다.
- 연관관계의 주인은 외래키가 존재하는 Order 이다.
- Order.member - ORDERS.MEMBER_ID
- OrderItem - Order
- 다대일 양방향 관계이다.
- OrderItem에 외래키가 있으므로 연관관계의 주인이다.
- OrderItem.item - ORDER_ITEM.ITEM_ID
- Order - Delivery
- 일대일 양방향 관계이다.
- Order가 연관관계의 주인이다.
- Category - Item
- @ManyToMany를 사용해 매핑.
- 실무에서는 @ManyToMany 사용 X. 예제를 위한 설계.
연관관계의 주인 복습
Member와 Team의 관계에서 Member와 Team은 N:1의 관계를 가진다.
N에 속하는 Member가 연관관계의 주인이 되어야 한다. N에 속하는 쪽에서 외래 키가 존재하기 때문이다.
Member의 Team을 수정하고 싶다고 가정하자. 만약 연관관계의 주인이 Team이라면 예를 들어 Team A에 있는 MemberList를 수정해야 한다.
테이블 입장에서 보면 Member에 있는 외래 키인 team_id만 수정하면 Team이 수정된다. 따라서 외래키가 있는 곳을 주인으로 설정해 수정 권한을 부여한다.
만약 반대로 설정한다면 Team B 객체의 어떤 부분을 수정했는데 Team B에 속하는 Member 컬럼들이 모두 우수수 의도치 않게 업데이트 될 수 있다.’
엔티티 설계 및 구현 주의 사항
임베디드 타입에는 기본 생성자가 필요하다.
임베디드 타입의 클래스에는 자바 기본 생성자를 public 또는 protected로 만들어두어야 한다.
JPA 구현 라이브러리가 객체를 생성할 때 리플렉션 같은 기술을 사용할 수 있도록 지원하기 위함이다.
protected로 해놓는 것을 권장한다.
엔티티에는 가급적 Setter 사용을 지양.
학습용이기 때문에 간단하게 하기 위해 Setter를 적용했는데, 실제로는 유지 보수가 어려워지는 단점이 있기 때문에 사용하지 말아야 한다.
모든 연관관계는 지연 로딩으로 설정
- 즉시 로딩은 예측이 어렵고, 어떤 SQL이 실행될 지 추적이 어렵다. 특히 JPQL에서 N+1 문제가 자주 발생된다.
- 모든 연관관계를 지연 로딩으로 설정해야 한다.
- 연관된 엔티티를 함께 DB에서 조회해야 한다면 fetch join 혹은 엔티티 그래프 기능을 활용해야 한다.
- @OneToOne, @ManyToOne 관계는 기본이 즉시 로딩으로 설정되어 있기 때문에 직접 지연 로딩으로 설정해주어야 한다.
컬렉션은 필드에서 바로 초기화 할 것
컬렉션은 필드에서 바로 초기화하는 것이 안전하다.
1
2
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
- null 문제에서 안전하다
- 하이버네이트는 엔티티를 영속화할 때 컬렉션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경한다.
- 만약 getOrders() 처럼 임의의 메서드에서 컬렉션을 잘못 생성하면 하이버네이트 내부 메커니즘에 문제가 발생할 수 있다.
- 내장 컬렉션으로 변경된 컬렉션과 실제 컬렉션간의 차이 발생 등.