프레임워크(Framework)/JPA

[JPA] 엔티티(Entity) 설계 시 주의사항

잇트루 2022. 12. 12. 02:20
반응형
본 내용은 온라인 강의 사이트 인프런의 김영한 님의 강의 내용이 포함되어 있습니다.
'실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발'
 

실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런 | 강의

실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다., - 강

www.inflearn.com

 

엔티티 설계 시 주의사항

Member 엔티티 예시

@Entity
@Getter @Setter
public class Member {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;

    private String name;

    @Embedded
    private Address address;

    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();

}

 

1. 연관관계 매핑은 최대한 단방향 매핑으로 설계를 끝낸다.

  • 단방향 매핑만으로도 연관관계 매핑은 완료된 된다.
  • 가능하면 최대한 단방향 매핑으로 설계를 끝낸다.
    • 양방향 매핑을 하지 않아도 테이블에 영향을 주지 않는다.
  • 단방향 매핑 후 필요한 경우에 양방향 매핑을 추가하는 식으로 설계한다.
    • 양방향 매핑은 객체 그래프 탐색 기능이 추가된 것이다.

 

 

2. Order 엔티티 작성 시 테이블명을 변경한다. (관례상 orders 또는 ORDERS)

  • @Table 어노테이션을 사용하여 테이블명을 orders”또는 ORDERS로 지정한다.
  • 데이터베이스의 order by로 인해 예약어로 사용되고 있는 경우가 많다.
  • 관례상으로 orders를 많이 사용할 뿐이고 다른 이름을 사용해도 된다.

 

 

3. 데이터베이스 테이블명과 컬럼명은 일관성 있게 지정한다.

  • 데이터베이스의 테이블명과 컬럼명은 일관성 있게 작성한다.
  • 관례상으로는 대문자 + _(언더스코어) 또는 소문자 + _(언더스코어)로 많이 사용한다.
    • ex) “MEMBER_ID” 또는 “member_id”

 

 

4. 테이블명, 컬럼명 생성 전략

스프링 부트에서는 다음과 같이 설정한다.

  • 카멜 케이스 → 언더스코어 (ex: memberId → member_id)
  • .(점) → _(언더스코어)
  • 대문자 → 소문자

 

 

5. 연관관계의 주인은 외래 키가 있는 곳으로 지정한다. (양방향 매핑 규칙)

  • 양방향 매핑 시 두 객체 중 하나는 연관관계로 주인으로 지정해야 한다.
  • 연관관계의 주인만 외래 키를 관리해야 한다. (등록, 수정)
    • 주인이 아닌 클래스는 읽기만 가능하다.
  • 외래 키가 있는 곳을 연관관계의 주인으로 정한다.
    • 다대일(N:1) 관계의 경우, 다(N)에 해당하는 쪽에 외래 키가 존재한다.
  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안 된다.

 

 

6. 모든 엔티티 식별자(ID)는 일관성 있게 작성한다.

  • Member 엔티티를 예로 들면, 식별자는 id 또는 memberId로 사용한다.
    • 다른 엔티티의 식별자도 일관성 있게 작성한다.
  • 엔티티의 식별자가 id인 경우 @Column 어노테이션을 통해 컬럼명을 member_id로 지정한다.
    • 데이터베이스 테이블은 타입이 없기 때문에 단순히 id로 모든 엔티티를 식별하면 구분하기 어렵다.
  • 식별자를 memberId와 같이 사용하면 컬럼명을 자동으로 member_id와 같이 지정해준다.
  • 중요한 것은 엔티티들의 식별자를 일관성 있게 작성하는 것이다.

 

 

7. 다대다(N:M) 연관관계 매핑은 다대일(N:1) + 일대다(1:N) - @ManyToMany

  • @ManyToMany 어노테이션은 사용하지 않는다.
  • 중간 테이블에 컬럼을 추가할 수 없으며, 세밀하게 쿼리를 실행하기 어렵기 때문이다.
  • 중간 엔티티를 만들고 @ManyToOne, @OneToMany로 양방향 매핑해서 사용한다.

 

 

8. 값 타입은 변경할 수 없도록 설계한다.

  • 값 타입은 변경 불가능하게 설계해야 한다.
  • Setter를 작성하지 않고, 생성자에서 값을 모두 초기화하도록 한다.
  • JPA 스펙상 엔티티나 임베디드 타입은 기본 생성자를 작성해야 한다.
  • 기본 생성자는 protected로 설정하여 구분 짓는 것이 좋다.

 

 

9. 엔티티에는 가급적 Setter를 사용하지 않는다.

  • 엔티티에는 가급적 Setter를 사용하지 않는다.
  • 모든 엔티티에 Setter가 있으면, 변경할 수 있는 포인트가 많아진다.
    • 유지보수가 어렵고 복잡해진다.
  • 이미 Setter가 많이 작성되어 있는 경우 리펙토링으로 최대한 Setter를 제거하는 것이 좋다.

 

 

10. 모든 연관관계는 지연로딩(LAZY)으로 설정한다.

  • 즉시로딩(EAGER)은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어렵다.
  • 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다.
  • 따라서 모든 연관관계는 지연로딩(LAZY)으로 설정한다.
  • @ManyToOne과 @OneToOne 어노테이션은 기본이 즉시로딩(EAGER)이다.
    • 어노테이션 속성을 fetch = FetchType.LAZY로 설정해야 한다.
  • 만약, 연관된 엔티티를 함께 조회해야 할 경우 fetch join 또는 엔티티 그래프 기능을 사용한다.

 

 

11. 컬렉션은 필드에서 초기화한다.

  • 컬렉션은 필드에서 바로 초기화하는 것이 안전하다.
    • NPE를 방지할 수 있다.(null 문제 해결)
  • 하이버네이트는 엔티티를 영속화(em.persist())할 때, 컬렉션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경한다.
Member member = new Member();
System.out.println(member.getOrders().getClass());

em.persist(member);
System.out.println(member.getOrders().getClass());
// 출력 결과
class java.util.ArrayList
class org.hibernate.collection.internal.PersistentBag
  • 일반적인 방법으로 생성된 객체와 영속성 컨텍스트에서 관리하는 객체는 서로 다른 컬렉션으로 출력된다.
    • getOrders()처럼 임의의 메서드에서 컬렉션을 잘못 생성하면 하이버네이트 내부 메커니즘에 문제가 발생할 수 있다.
  • 따라서 필드 레벨에서 생성하는 것이 가장 안전하고, 코드도 간결하다.
  • 필드에서 초기화된 컬렉션은 변경하지 않고 그대로 사용해야 한다.
반응형