Skip to content

JPA Section2

SH-Seol edited this page May 25, 2024 · 1 revision

도메인 모델과 테이블 설계

Untitled

  • 다대다의 관계는 1:N, N:1로 분할하는 것이 좋다.
  • 양방향 매핑은 하지 마라.

Untitled

  • 객체에서는 Category와 Item모두 서로를 list로 가져도 되지만, RDBMS에서는 중간에 mapping table을 두어야 한다.

연관관계 매핑 분석(중요)

  • 1:N, N:1의 관계에서는 foreign key가 있는 것이 연관관계의 주인인 것이 좋다.

    • ex. OrderItem, OrderMember, OrderDelivery
    • 화살표는 단방향 관계를 의미
    • 실무에서 @ManyToMany사용하지 마라.

    외래 키가 있는 곳을 연관관계 주인으로 정해라. 연관관계의 주인은 단순히 외래 키를 누가 관리하냐의 문제이지 비즈니스 상 우위가 아니다.일대다 관계에서 항상 다쪽에 외래 키가 있다.

엔티티 클래스 개발1

package jpabook.jpashop.domain;

import jakarta.persistence.Embeddable;
import lombok.Getter;

@Embeddable
@Getter
public class Address {
    private String city;
    private String street;
    private String zipcode;
}
package jpabook.jpashop.domain;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String name;

    @Embedded
    private Address address;

    private List<Order> orders = new ArrayList<>();

}
  • @Embeddable, @Embedded는 JPA의 내장 타입임을 나타냄 하나만 사용해도 되지만 보통 둘 다 해준다.
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jakarta.persistence;

public enum InheritanceType {
    SINGLE_TABLE,
    TABLE_PER_CLASS,
    JOINED;

    private InheritanceType() {
    }
}
  • SINGLE_TABLE: 한 테이블에 다 넣는 것
  • TABLE_PER_CLASS: Class마다 table생성
  • JOINED: 가장 정교화된 스타일
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package jakarta.persistence;

public enum EnumType {
    ORDINAL,
    STRING;

    private EnumType() {
    }
}
  • ORDINAL: enum이 숫자로 들어감. 만약 enum에서 다른게 생기면 망할 수 있음
  • STRING: string으로 받음. 새로운 것이 추가되어도 문제 없음.

쿼리 parameter 로그 남기기

쿼리 파라미터 로그 남기는 것은 시스템 자원을 사용하므로 개발 단계에서는 편히 사용해도 되지만,

운영시스템에 적용하려면 성능 테스트가 필요하다.

@ManyToMany
    @JoinTable(name = "category_item")
    private List<Category> categories = new ArrayList<>();
  • jointable을 실무에서 쓰지 않는 이유: 단순하게 매핑하고 끝날 일이 없음. 등록 날짜 등을 넣어야함.
  • Foreign key를 꼭 걸어야 하는가?
    • 실시간 트래픽, 잘 서비스되는 것이 중요하면 foreign key 빼고, 인덱스만 잘 잡아주면 된다.
    • 돈과 관련되고 항상 데이터가 맞아야 한다면 foreign key를 넣어야 한다.

엔티티 클래스 개발

실무에서는 엔티티의 데이터 조회가 너무 많기에 getter는 모두 여는 것이 편하다. setter는 호출하면 데이터가 바뀌기 때문에 다르다. setter대신 변경 지점이 명확하도록 변경을 위한 비즈니스 메서드를 별도로 제공하는 것이 좋다.

  • sql에서 단순히 id라고 하면 관리를 하기 어렵다. member객체라면 member_id로 하는 것이 좋다.
  • 실무에서는 ManyToMany 사용하지 마라. 중간테이블에 값을 더 넣을 수 없다.
  • @Embeddable이나 엔티티의 경우 기본생성자를 public, protected로 설정해라.

엔티티 설계시 주의점

  • 엔티티에는 가급적 Setter를 사용하지 말자
    • 유지보수가 매우 어렵다.
  • 모든 연관관계는 지연로딩(LAZY)으로 설정하자
    • 즉시로딩(EAGER)은 예측이 어렵다.
    • 연관된 엔티티와 함께 DB를 조회해야 한다면 fetch.join을 해야한다.
    • XXToOne은 기본적으로 즉시로딩 관계이므로, 지연로딩으로 바꿔야 한다.
      • OneToXX는 지연로딩이 기본
      • n+1문제도 생긴다.
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String name;

    @Embedded
    private Address address;

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
    
    //Member member(){
	  //  orders = new ArrayList<>();
    //} 이거 안씀

}
  • private List orders = new ArrayList<>(); 이게 국룰
    • 바로 초기화 하는 것이 안전
    • null문제에서도 안전
    • Hibernate가 제공하는 내장 컬렉션으로 변경 → 원하는 매커니즘대로 동작
  • cascade?
package jpabook.jpashop.domain;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

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 = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @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;

    private LocalDateTime orderDate;

    private OrderStatus status;//주문상태 [ORDER, CANCEL]
}
  • cascade = CascadeType.ALL
    • casecade를 하지 않는다면 pesist(orderA), persist(orderB), persist(order)를 각각 호출해야 하지만, cascade를 두면 persist(order)만 하면 된다. ALL이기에 Delete하면 같이 지워짐