[TIL] - 엔티티 설계시 주의점

김주형·2022년 9월 18일
0

TIL

목록 보기
13/37
post-thumbnail

Reference

다음을 참조하여 작성하였습니다. 감사합니다.🙇🏻‍♂️


엔티티에는 Setter 사용을 지양

  • 변경 포인트가 너무 많아 유지보수가 어려움
  • Setter 대신 별도의 비즈니스 메소드 제공
  • Setter를 열어놓고, 나중에 리펙토링을 통해 제거하는 것도 방법

모든 연관관계는 지연로딩으로 설정(많은 장애를 해결할 정도로 중요,,)

  • 즉시로딩(EAGER)는 예측이 어렵고 어떤 SQL이 실행될지 추적하기 어려움

  • JPQL의 경우 FetchType과는 별개로 입력한 SQL이 그대로 실행되므로 EAGER일 경우 한 번 SQL이 실행되는 N + 1 문제 발생

  • 실무에서는 모든 연관관계를 지연로딩(LAZY)로 설정

  • 연관된 엔티티를 함께 조회해야 할 경우 fetch join 또는 엔티티 그래프 기능 사용

  • fetch join : 조회한 엔티티를 객체 그래프 접근 방식으로 접근하는 방법

  • 엔티티 그래프 : 원하는 그룹만 조회하는 방법

  • @XToOne은 기본이 즉시 로딩이므로 fetch = FetchType.LAZY로 직접 지연로딩으로 설정

  • 컬렉션은 필드에서 초기화

  • 필드에서 바로 초기화 하는 것이 null 문제에서 안전

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
  • 하이버네이트는 엔티티를 영속화 할 때, 컬렉션을 하이버네이트가 제공하는 내장 컬렉션으로 변경
  • 엔티티의 기존 컬렉션을 삭제할 경우 하이버네이트 내장 컬렉션이 아니게 되어 문제가 발생

테이블, 컬럼명 생성 전략

하이버네이트 기존 구현

  • SpringPhysicalNamingStrategy : 엔티티의 필드명을 그대로 테이블의 컬럼명으로 사용(스프링 부트 엔티티 설정)

카멜 케이스 -> 언더스코어(memberPoint -> memberpoint)
.(점) ->
(언더스코어)
대문자 -> 소문자
논리명 생성, 물리명 적용

  1. 논리명 생성
  • ImplicitNamingStrategy 사용
  • 명시적으로 컬럼, 테이블명을 직접 적지 않을 경우
    spring.jpa.hibernate.naming.implicit-strategy : 테이블이나 컬럼명을 명시하지 않을 때 논리명 적용
  1. 물리명 적용 - spring.jpa.hibernate.naming.physical-strategy : 모든 논리명에 적용되며 실제 테이블에 적용
    스프링 부트 기본 설정
// application.properties

spring.jpa.hibernate.naming.implicit-strategy:
org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy

spring.jpa.hibernate.naming.physical-strategy:
org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
Cascade

특정 엔티티의 영속성 상태가 변경되었을 때,
이를 연관된 엔티티에도 전파시킬지 선택하는 옵션.
연관관계 편의 메소드

양방향 관계의 경우 양쪽 엔티티에 모두 값을 입력해야 객체 그래프 탐색 가능,
편하게 값을 입력하기 위한 메소드


예제

주문 엔티티

@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; // 주문시간

    @Enumerated(EnumType.STRING)
    private OrderStatus status; // 주문상태[ORDER, CANCEL]

    // == 연관관계 메소드 == //
    public void setMember(Member member) {
        this.member = member;
        member.getOrders().add(this);
    }

    public void addOrderItem(OrderItem orderItem) {
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }

    public void setDelivery(Delivery delivery) {
        this.delivery = delivery;
        delivery.setOrder(this);
    }
}

카테고리 엔티티

@Entity
@Getter
@Setter
public class Category {

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

    private String name;

    @ManyToMany
    @JoinTable(
            name = "category_item",
            joinColumns = @JoinColumn(name = "category_id"),
            inverseJoinColumns = @JoinColumn(name = "item_id")
    )
    private List<Item> items = new ArrayList<>();

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private Category parent;

    @OneToMany(mappedBy = "parent")
    private List<Category> child = new ArrayList<>();

    // == 연관관계 메소드 == //
    public void addChildCategory(Category child) {
        this.child.add(child);
        child.setParent(this);
    }
}
profile
근면성실

0개의 댓글