Search
Duplicate
📒

[JPA 활용 1] 01-2. 도메인 분석 코드작성

상태
수정중
수업
JPA
주제
JPA
4 more properties
참고

Entity 클래스 개발

Member - Order 연관관계(1:N)

NOTE
OrderMember의 관계는 N:1(다대일)이다.
N에 해당하는 Order가 연관관계의 주인으로 채택되며 양방향 연관관계(1에 해당하는 Member도 Order의 정보를 컬렉션으로 갖고 있다)로 설정되어 있다.
연관관계의 하인(mappedBy - 지정한 필드)은 수정이 발생해도 값이 변경되지 않는다.

Order - Delivery 연관관계(1:1)

NOTE
하나의 주문은 하나의 배송을 한다. 즉 1:1 연관관계로 표현할 수 있다.
이 때 연관관계의 주인은 access가 많은 즉, 조회할 일이 많은 곳을 선택해서 주인으로 선택하면 된다. ( 해당 예제에서는 Delivery가 조회가 많을거라 판단해 OrderDelivery가 연관관계의 주인이된다.)
[ 참고]
Delivery EntityDeliveryStatus가 ENUM 클래스로 되어있다.
해당 내용은 다음과 같으며, EnumType.STRING을 해줘야 순서 보장이 된다!
public enum DeliveryStatus { READY, COMP }
Java
복사
EnumType.ORDINAL도 존재하는데 이는 순서에 따라 0,1,2.. 값을 매긴다.
더 편해 보일 수 있지만 데이터가 추가되는순간 순서가 섞이면서 위험해지니 꼭 STRING을 쓰자.

Address(값 타입)

@Embeddable @Getter public class Address { private String city; private String street; private String zipcode; // JPA의 기본 전략인 기본 생성자가 있어야 reflector 나 프록시를 사용할 수 있다. (puliic / protected 가능) protected Address(){} public Address(String city, String street, String zipcode) { this.city = city; this.street = street; this.zipcode = zipcode; } } //... @Enumerated(EnumType.STRING) private DeliveryStatus status;
Java
복사
값 타입을 사용하기 위해 @Embeddable을 지정하였고, Delivery에서 Address 컬럼에 @Embedded를 선언하였다.
값 타입 사용시 불변의 설계가 올바르기 떄문에 생성자를 통해 생성하고, 수정을 못하게 막도록한다. (단 JPA기본 전략떄문에 기본생성자는 있어야 한다.)
[ 참고]
값 타입(Embeded)를 사용하는 이유는 주소라는 값 타입 아래 하위 데이터들을 명확하게 표현하기 위해서이다.
배송 Entity(주문정보, 도서, 도로명, 우편번호, 배송상태) → 배송 Entity(주문정보, 주소, 배송상태)

Order, OrderItem, Item (N:N 분리)

NOTE
OrderItem의 관계를 살펴보자
하나의 주문은 여러 상품을 가질 수 있고, 하나의 상품은 여러 주문을 가질 수 있다.
이는 N:N(다대다) 관계이며, 이를 풀기위해 OrderItem을 두어 N:N → 1:N으로 나누어서 처리한다.
OrderItem의 입장에서 OrderItem의 연관관계 주인이 되었다.
OrderOrderItem양방향 연관관계가 되어있지만, OrderItemItem은 단방향 관계이기 때문에 Item에 별도의 OrderItem 컬렉션이 없는것을 볼 수 있다.

Category, Item 연관관계(N:N)

NOTE
CategoryItem의 관계는 N:N이다.
별도의 CategoryItem을 만들어서 N:N을 풀어서 사용하는 것이 올바른 예시이지만, 해당 경우도 예제에서 다루기 때문에 적용해보자
RDBMS에선 컬렉션처럼 N:N관계를 사용할 수 없기 떄문에 @JoinTable와 같이 중간 테이블을 만들어준다.
// Categoty.java @ManyToMany @JoinTable(name = "category_item", joinColumns = @JoinColumn(name = "categoy_id"), inverseJoinColumns = @JoinColumn(name = "item_id") ) private List<Item> items = new ArrayList<>();
Java
복사
joinCoumns : 현재 PK로 사용할 컬럼 지정
inverseJoinColumns: 반대편에서 PK로 사용할 컬럼 지정
Category구조가 계층 구조기 떄문에 자기의 부모(@ManyToOne), 자기의 자식(@OneToMany) 즉, 자기 자신을 양방향 연관관계로 설정했다.
item Entity를 살펴보면 하위 타입들을 지정하여 싱글 테이블 전략을 사용한다.
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "dtype") @Getter @Setter public 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<>(); }
Java
복사
Item.java
@Entity @DiscriminatorValue("M") @Getter @Setter public class Movie extends Item { private String director; private String actor; }
Java
복사
Movie.java
@Inheritance (상속 전략)
 디폴트가 싱글테이블, JOINED, TABLE_PER_CLASS 등 존재
@DiscriminatorColumn 
하위 클래스를 구분하기 위한 컬럼명 지정 (부모에서 설정)
하위 엔티티들은 Item을 상속 받고 @DiscriminatorValue() 를 지정하여 사용될 데이터를 지정

Entity 설계시 주의점

NOTE
Entity는 가급적 Setter를 사용하지 말자
모든 연관 관계는 지연 로딩으로 설정
즉시로딩(Eager)은 예측이 어렵고, 어떤 SQL이 실행될지 추적이 어렵다.
JPQL에서 N+1문제 자주 발생
지연로딩(LAZY)으로 꼭 설정해주자.
연관된 Entity를 함께 DB에서 조회해야하면, fetch join 또는 Entity Graph기능 사용
@~ToOne 관계는 기본이 즉시 로딩이므로, 직접 지연로딩을 설정해야한다.
컬렉션은 필드에서 초기화 하자.
@OneToMany(mappedBy = "member") // 선택 Null 문제에서 안전, 하이버네이트 기능 문제 해결 private List<Order> orders = new ArrayList<>(); /* public Member() { orders = new ArrayList<>(); } */
Java
복사
컬렉션을 초기화 하는 방법은 필드, 생성자 2가지가 존재한다.
아래의 이유로 필드에서 초기화 하는것이 best이다.
Member member = new Member(); System.out.println(member.getOrders().getClass()); em.persist(team); //영속성 컨텍스트에서 관리 System.out.println(member.getOrders().getClass()); /* - 출력 결과 class java.util.ArrayList class org.hibernate.collection.internal.PersistentBag */
Java
복사
하이버네이트는 컬렉션이 영속성 컨텍스트에 들어오면 추적을 위해 컬렉션을 감싸 내장컬렉션으로 변경한다.

테이블, 컬럼명 생성 전략

NOTE
스프링 부트 설정( Entity 필드 → 테이블 컬럼 )
카멜 케이스 → 언더 스코어 ( ex memberPoint → member_point )
. → _
대문자 → 소문자

논리명 생성

명시적으로 컬럼, 테이블 명을 적지 않으면 ImplicitNameStrategy 사용
spring.jpa.hibernate.naming.implicit-strategy 
테이블이나, 컬럼명을 명시하지 않을 때 논리명 적용

물리명 적용

spring.jpa.hibernate.naming.physical-strategy 
모든 논리명에 적용됨, 실제 테이블에 적용

CascadeType 설정

NOTE
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL) private List<OrderItem> orderItems = new ArrayList<>(); @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "delivery_id") private Delivery delivery;
Java
복사
orderItemsdelivery필드에 CascadeType.ALL을 설정
해당 기능은 Orderpersist()할 때 연관된 컬렉션들이 같이 영속화 하며, 삭제할 때 같이 삭제하게 됨.

연관관계 편의 메서드

NOTE
/* Member member = new Member(); Order order = new Order(); member.getOrders().add(order); // 연관관계의 하인인 Member엔티티의 Orders 컬렉션에 order를 넣어주는 기능 order.setMember(member); // 연관관계의 주인인 Order 엔티티의 member 컬렉션에 order를 넣어주는 기능 */ public void setMember(Member member) { // Order 엔티티 기준 this.member = member; // 현재의 member 컬렉션에 넣어준다. member.getOrders().add(this); // member에서 Member 엔티티의 Orders를 탐색하여 member를 넣어준다. }
Java
복사
양방향 연관관계를 구현했을 때, 한 곳에 데이터를 생성하면 반대편에도 값을 넣어주어야 한다.
해당 부분을 빠뜨리기 쉬운 부분이므로 메서드 안에서 양쪽 모두 넣어주는 기능을 구현해주는 것이 바람직하다.