엔티티 설계 시 주의사항

정훈희·2022년 10월 29일
0

Spring

목록 보기
14/24
post-thumbnail
post-custom-banner

참조


실무에서는 getter는 대부분 열어두고 setter는 꼭 필요한 경우에만 사용하기

사실 getter setter대신 필요한 별도의 메서드를 제공하는 것이 가장 이상적이지만

데이터를 조회할 일이 너무 많으므로 getter는 모두 열아두는것이 편함(getter는 아무리 호출해도 큰 문제는 발ㅇ생하지 않음)

하지만 setter는 엔티티의 값이 어디서 변했는지 추적하기 힘들어짐 → 변경지점이 명확하도록 변경을 위한 비즈니스 메서드를 별도로 제공해야함


@Entity @Getter @Setter
public class Member {
  @Id
  @Column(name = "member_id")
  @GeneratedValue
  private Long id;
	...
}

예제에서는 엔티티의 식별자는 id 를 해놓고 pk 컬럼명은 member_id를 사용함

→ FK랑 이름도 맞추고, 컬럼명을 그냥 id로 해놓으면 나중에 찾기 힘들고, DB설계하는 분들이 관례적으로 이렇게 많이함


실무에서는 @ManyToMany 사용X

왜냐면 ManyToMany는 중간테이블에 다른 컬럼을 못넣음(두 테이블의 PK만 들어감)

→ 중간 엔티티를 만들고 @ManyToOne, @OneToMany로 매핑해서 사용하기


@Embeddable // Jpa 내장타입
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Address {
  private String city;
  private String street;
  private String zipcode;
}

위와 같은 값 타입(Value Type)은 변경이 되선 안됨

→ Setter 는 막고 생성자에서 값을 모두 초기화하여 변경 불가능하게 만들기

  • 참고: JPA는 리플렉션이나 프록시 같은 기술을 사용하기 위해 기본 생성자가 필요함 → Address에 생성자를 넣고 추가적으로 기본 생성자도 넣기 → 근데 기본생성자를 public으로 하면 다른곳에서 호출 될수도 있으므로 protected로 설정

모든 연관관계는 지연로딩으로 설정!(중요)

즉시 로딩(EAGER)이란 엔티티 하나를 로딩하는 시점에 연관된 다른 엔티티들을 한번에 로딩 하는 것이다.

예를 들면, member를 조회할 때 연관된 order와 다른 엔티티를 한번에 조회해버림

즉시 로딩은 예측이 어렵고, 어떤 SQL이 실행될지 모르고 JPQL 실행 시 N+1문제 자주 발생

@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {
  @Id
  @GeneratedValue
  @Column(name = "order_id")
  private Long id;

  @ManyToOne(fetch = FatchType.EAGER)
  @JoinColumn(name = "member_id")
  private Member member;

  ...
}

order를 조회할떄 member를 join해서 한방에 같이 가져옴

  • em.find(order_id) 이런경우만 해당

But JPQL 사용시 select o from order o; → SQL로 그대로 번역이됨

→ order가 100개가 있고 Member가 EAGER로 되어있으면 쿼리가 100번 날라감

→ order에 연관된 Member를 가져오기 위해 쿼리가 100번 날라감

→ N+1 문제 발생

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

연관된 엔티티를 함께 DB에서 조회해야 하면 fetch join, 또는 엔티티 그래프 기능을 사용한다.

  • XToOne 관계는 기본이 즉시 로딩이므로 직접 설정해서 바꿔야됨
  • @ManyToOne(fetch = FatchType.LAZY) ← 이런식으로 바꾸기
  • JPQL(Java Persistence Query Language): 테이블이 아닌 엔티티를 대상으로 검색하는 객체지향 쿼리로 SQL을 추상화 해서 특정 DB의 SQL에 의존하지 않음

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

  • null 문제 해결
  • Hibernate는 Entity를 영속화 할 때, 컬렉션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경함 → getOrders() 처럼 임의의 메서드에서 컬렉션을 잘못 생성하면 Hibernate 내부 메커니즘에 문제가 발생할 수 있음 ex)
    Member member = new Member
    System.out.println(member.getOrders().getClass());
    em.persist(team);
    System.out.println(member.getOrders().getClass());
    
    // out put
    class java.util.ArrayList
    class org.hibernate.collection.internal.PersistentBag
    위와 같이 영속화 시키면 Hibernate의 내장 컬렉션으로 변경됨 변경된 상태에서 getTeam() 같은 메서드를 호출하며 new ArrayList<>() 이런식으로 생성해 버리면 문제가 발생할 수 있음 그러므로 컬렉션은 필드에서 바로 초기화하자!

14분 까지 완료

Spring Boot Reference Guide

Index of /hibernate/orm/5.4/userguide/html_single

  • 테이블 컬럼명 생성 전략 과거 하이버네이트 기존 구현
    • 엔티티의 필드명을 그대로 테이블 명으로 사용

      현재 스프링 부트 신규 설정(엔티티(필드)→테이블(컬럼))

    • 카멜 케이스 → 언더스코어(userId → user_id)

    • 대문자 → 소문자

    • . → _

      적용 2단계

    • 논리명 적용: 테이블이나 컬럼명을 명시하지 않을 때 논리명 적용

    • 물리명적용 : 모든 논리명에 적용, 실제 테이블에 적용


cascade 옵션

...
public class Order {
  @Id
  @GeneratedValue
  @Column(name = "order_id")
  private Long id;

  @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
  private List<OrderItem> orderItems = new ArrayList<>();
	...
}

위의 코드에서는 orderItems를 생성할때

부모 엔티티인 Order가 영속 상태로 만들 때 연관된 엔티티도(여기선 orderItems의 OrderItem 엔티티들) 함께 영속 상태로 전이시킴

즉 cascade = CascadeType.ALL 를 적용하면 특정 엔티티에 대해 특정한 작업을 수행하면 관련된 엔티티에도 동일한 작업을 수행한다는 의미


연관관계 메소드

ex) Member가 주문을하면 orders에 order 추가(연관관계의 주인이 아니더라도 값은 참조하므로 set을 해줘야함)

profile
DB를 사랑하는 백엔드 개발자입니다. 열심히 공부하고 열심히 기록합니다.
post-custom-banner

0개의 댓글