아래는 "엔티티 설계 시 고려사항"에 대해 설명하는 글입니다. 실무에서의 경험과 JPA의 특성을 반영해 실용적인 내용을 담았습니다.

---

### 엔티티 설계 시 고려사항

JPA를 사용해 엔티티를 설계할 때는 단순히 테이블과 객체를 매핑하는 것을 넘어, 객체지향 원칙, 성능, 유지보수성, 그리고 데이터베이스의 특성을 모두 고려해야 합니다. 잘못된 설계는 코드 복잡성을 높이거나 성능 문제를 일으킬 수 있으므로, 초기 설계 단계에서 신중한 접근이 필요합니다. 아래는 엔티티 설계 시 주요 고려사항입니다.

#### 1. 객체지향 원칙 준수
엔티티는 데이터베이스 테이블뿐만 아니라 객체지향의 개념을 반영해야 합니다.
- **캡슐화**: 필드를 `private`으로 설정하고, getter/setter를 통해 접근을 제어합니다. 불필요한 setter는 제거해 데이터 무결성을 보호합니다.
- **도메인 주도 설계(DDD)**: 엔티티가 단순한 데이터 홀더가 아니라 비즈니스 로직을 포함하도록 설계합니다. 예를 들어, `Order` 엔티티에 주문 상태 변경 메서드를 추가합니다.
- **불변성**: 가능하면 생성 시점에 값을 설정하고 변경을 최소화합니다(예: `@Setter` 대신 생성자 사용).

#### 2. 식별자(기본 키) 전략
기본 키는 엔티티의 고유성을 보장하며, 설계 시 다음을 고려합니다:
- **자연 키 vs 대리 키**: 주민번호 같은 자연 키는 고유하지만 변경 가능성이 있으므로, 의미 없는 대리 키(예: `Long` 타입 ID)를 사용하는 것이 일반적입니다.
- **생성 전략**: `@GeneratedValue`로 `IDENTITY`나 `SEQUENCE`를 선택합니다. 대량 삽입이 많다면 `SEQUENCE`가 유리할 수 있습니다.
- **타입 선택**: `Long` 같은 래퍼 타입을 사용해 `null` 가능성을 명확히 하고, 기본형(`long`)의 0 초기화 문제를 피합니다.

#### 3. 연관관계 설계
엔티티 간 관계는 JPA의 강점 중 하나지만, 잘못 설계하면 성능 저하나 복잡성이 증가합니다.
- **단방향 vs 양방향**: 가능하면 단방향 매핑을 우선 사용합니다. 양방향은 필요 시에만 추가하고, `mappedBy`로 관계의 주인을 명확히 지정합니다.
- **지연 로딩(Lazy Loading)**: `@OneToMany`와 `@ManyToMany`는 기본적으로 지연 로딩을 사용해 불필요한 데이터 조회를 방지합니다. 필요 시 `fetch = FetchType.EAGER`를 고려하되, N+1 문제를 주의합니다.
- **N:M 관계**: 실무에서는 중간 엔티티를 만들어 1:N, N:1로 분리하는 것이 유지보수와 확장성 측면에서 유리합니다.

#### 4. 성능 최적화
엔티티 설계가 성능에 미치는 영향은 크므로 다음을 고려합니다:
- **필드 수 최소화**: 불필요한 컬럼은 추가하지 않습니다. 예를 들어, 임시 계산 값은 엔티티에 저장하지 않고 DTO로 처리합니다.
- **인덱스 활용**: 자주 조회되는 컬럼(예: 외래 키, 검색 조건)에 `@Index`를 추가하거나 데이터베이스에 직접 설정합니다.
- **배치 크기 설정**: `@BatchSize`를 사용해 1:N 관계 조회 시 N+1 문제를 완화합니다.

#### 5. 데이터 무결성과 제약 조건
데이터베이스와 엔티티 간 일관성을 유지하려면 제약 조건을 반영해야 합니다:
- **`@Column(nullable = false)`**: 필수 입력 필드를 명시합니다.
- **`@Column(unique = true)`**: 고유 제약 조건을 설정합니다(예: 이메일).
- **Cascade 옵션**: `@OneToMany(cascade = CascadeType.ALL)`로 연관 엔티티를 자동 관리할 수 있지만, 과도한 사용은 예기치 않은 삭제를 유발할 수 있으니 주의합니다.

#### 6. 값 타입 활용
단순 데이터는 값 타입(Embedded Type)으로 설계해 재사용성과 가독성을 높입니다.
- 예: 주소(`Address`)를 별도 클래스로 분리.
  ```java
  @Embeddable
  public class Address {
      private String city;
      private String street;
      // getter, constructor
  }

  @Entity
  public class Member {
      @Id
      private Long id;
      @Embedded
      private Address address;
  }
  ```
- 값 타입은 불변 객체로 설계해 부작용을 방지합니다.

#### 7. 실무적 현실성
실무에서는 이상적인 설계와 현실적 제약 사이에서 균형을 맞춰야 합니다:
- **기존 테이블 매핑**: 레거시 데이터베이스와의 호환성을 위해 테이블 구조를 그대로 반영할 수 있습니다. `@Table`과 `@Column`으로 조정합니다.
- **버전 관리**: `@Version`을 사용해 낙관적 락(Optimistic Locking)을 적용하면 동시성 문제를 방지할 수 있습니다.
- **테스트 용이성**: 엔티티가 복잡해지면 단위 테스트 작성이 어려워지므로, 단순하고 명확하게 유지합니다.

#### 8. 확장성과 유지보수성
미래의 요구사항 변화를 예측해 유연한 설계를 추구합니다:
- **상속 매핑**: `@Inheritance`로 `SINGLE_TABLE`이나 `JOINED` 전략을 사용해 엔티티 계층을 설계합니다. 예: `Item`과 하위 클래스 `Book`, `Electronics`.
- **공통 속성 추출**: `@MappedSuperclass`로 공통 필드(예: 생성일, 수정일)를 상속받아 재사용합니다.

#### 예시: 실무적 엔티티 설계
```java
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Column;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Embedded;

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

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id", nullable = false)
    private Member member;

    @Embedded
    private Address deliveryAddress;

    @Column(name = "order_date", nullable = false)
    private LocalDateTime orderDate;

    // 비즈니스 로직
    public void cancel() {
        if (deliveryAddress != null) {
            throw new IllegalStateException("배송 중에는 취소 불가");
        }
        // 상태 변경 로직
    }

    // 생성자, getter
}
```

#### 설계 포인트
- `member`는 지연 로딩으로 성능 최적화.
- `deliveryAddress`는 값 타입으로 재사용성 확보.
- `cancel()` 메서드로 도메인 로직 포함.

---

엔티티 설계는 JPA 활용의 첫걸음이자 성공적인 프로젝트의 기반입니다. 위 고려사항을 바탕으로 객체와 데이터베이스 간 균형을 맞춘다면, 유지보수성과 성능을 모두 충족하는 설계를 완성할 수 있습니다. 다음 장에서는 연관관계 매핑 심화와 실무 패턴을 다뤄보겠습니다.

---

책의 흐름에 맞는지, 추가 내용이나 수정이 필요하면 말씀해 주세요!