반응형
본 내용은 온라인 강의 사이트 인프런의 김영한 님의 강의 내용이 포함되어 있습니다.
'실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발'
엔티티 설계 시 주의사항
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()처럼 임의의 메서드에서 컬렉션을 잘못 생성하면 하이버네이트 내부 메커니즘에 문제가 발생할 수 있다.
- 따라서 필드 레벨에서 생성하는 것이 가장 안전하고, 코드도 간결하다.
- 필드에서 초기화된 컬렉션은 변경하지 않고 그대로 사용해야 한다.
반응형
'프레임워크(Framework) > JPA' 카테고리의 다른 글
[JPA] JPQL 프로젝션(Projection) - 객체지향 쿼리 언어 JPQL (2) (0) | 2022.12.14 |
---|---|
[JPA] JPQL이란? - 객체지향 쿼리 언어 JPQL (1) (0) | 2022.12.13 |
[JPA] 값 타입 컬렉션 - 값 타입 (4) (0) | 2022.12.11 |
[JPA] 값 타입의 비교 - 값 타입 (3) (0) | 2022.12.10 |
[JPA] 값 타입과 불변 객체 - 값 타입 (2) (0) | 2022.12.09 |