7주차 Unit 4.1 — @Entity 와 엔티티 개념

Psj·2026년 6월 1일

F-lab

목록 보기
224/239

Unit 4.1 — @Entity 와 엔티티 개념

F-LAB JAVA · 7주차 · Phase 4 · JPA 엔티티 매핑
🏷️ Phase 4 시작 — 객체를 JPA 세계로


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • @Entity 어노테이션의 의미는?
  • 엔티티 (Entity) 의 정의는?
  • 엔티티의 3가지 조건 은?
  • 기본 생성자 필수 인 이유는?
  • final 클래스 X 인 이유 (프록시) 는?
  • DTO 와 Entity 의 차이 는?
  • @Table 어노테이션은?
  • 이름 명시 vs 자동 은?
  • 엔티티 라이프사이클 은?

🎯 핵심 한 문장

@Entity 는 이 클래스가 JPA 가 관리하는 영속성 객체 (엔티티) 임을 선언하는 어노테이션으로 (1) @Entity 어노테이션 (2) 기본 생성자 필수 (JPA 가 리플렉션으로 객체 생성) (3) final 클래스 금지 (JPA 가 프록시를 만들어야) 의 3가지 조건을 충족해야 하며, DTO 와 달리 영속성 컨텍스트가 관리하고 변경 감지 (Dirty Checking) 의 대상이 된다.
@Entity 는 자바 클래스를 JPA 가 관리하는 영속성 객체 (엔티티) 로 선언하는 어노테이션이다.
이 어노테이션이 붙으면 JPA 가 해당 클래스를 DB 의 테이블과 매핑 하며 영속성 컨텍스트에서 관리한다.
엔티티의 3가지 조건은 — (1) @Entity 어노테이션 (필수), (2) 기본 생성자 (no-arg constructor) 필수 (JPA 가 리플렉션으로 객체 생성 하기 위해 — 파라미터 없는 생성자 호출 후 setter 또는 리플렉션 필드 접근), (3) final 클래스 금지 (JPA 가 Lazy Loading 등을 위해 프록시 (자식 클래스) 를 만드는데 final 은 상속 불가).
DTO 와 결정적 차이는 — DTO 는 단순 데이터 전달용 객체로 영속성 X, Entity 는 영속성 컨텍스트가 관리하고 변경 감지 (Dirty Checking) 의 대상 이라 entity.setStatus(...) 만으로 트랜잭션 commit 시 자동 UPDATE 된다.
@Table(name = "...") 은 매핑 테이블명 명시이며, 생략하면 Spring Boot 의 명명 전략에 따라 클래스명을 snake_case 로 변환한 테이블에 매핑된다 (Shipment → shipments).

비유 — 시민 등록 (영속 시민 vs 방문자)

@Entity = 시민 등록증:

비유:
  - 자바 객체 = 사람
  - JPA 영속성 컨텍스트 = 국가
  - @Entity = 시민 등록

DTO (방문자):
  - 데이터 전달만
  - 일시적
  - 국가가 관리 X
  - 떠나면 끝

Entity (영속 시민):
  - 등록 (@Entity)
  - 국가가 관리
  - 변경 추적 (Dirty Checking)
  - 데이터 영구 보관 (DB)

3가지 조건:
  1. 등록증 (@Entity 어노테이션)
  2. 신원 확인 (기본 생성자 필수)
     - JPA 가 객체 만들 때 필요
  3. 가족 등록 (final X)
     - JPA 가 프록시 만들어야

@Table:
  - 등록할 지역 (테이블) 명시
  - 생략 시 자동 (이름 규칙)

라이프사이클:
  - 비영속 (new): 시민 X (자바 객체만)
  - 영속 (managed): 시민 등록
  - 준영속 (detached): 시민권 일시 정지
  - 삭제 (removed): 사망 신고

ILIC:
  - Shipment, Customer, Booking 모두 엔티티
  - 102 테이블 × @Entity

→ @Entity = JPA 관리 대상 선언, 3가지 조건, DTO 와 결정적 차이.


🧭 9개 섹션 로드맵

1. @Entity 정의
2. 엔티티 의미
3. 3가지 조건
4. 기본 생성자 필수
5. final 클래스 X (프록시)
6. DTO vs Entity
7. @Table
8. 이름 명시 vs 자동
9. 엔티티 라이프사이클

1️⃣ @Entity 정의

1.1 정의

@Entity:

  자바 클래스를:
    - JPA 가 관리하는 객체로 선언
    - DB 테이블과 매핑
    - 영속성 컨텍스트 관리 대상

→ JPA 의 시작점

1.2 어노테이션 위치

import jakarta.persistence.Entity;

@Entity   // 클래스 레벨
public class Shipment {
    // ...
}

// 패키지:
// jakarta.persistence.Entity (Spring Boot 3+)
// 또는 javax.persistence.Entity (Spring Boot 2)

1.3 효과

효과:

  JPA 인식:
    - 영속성 컨텍스트 관리
    - SQL 자동 생성 (find, persist 등)
    - 1차 캐시 대상
    - Dirty Checking 대상

→ 단순 객체 → 엔티티

1.4 ILIC 의 맥락

// @Entity 적용 (ILIC)
import jakarta.persistence.Entity;
import jakarta.persistence.Id;

@Entity
public class Shipment {
    @Id
    private Long id;
    
    private String blNo;
    private String status;
    // ... 필드들
    
    // 기본 생성자 (필수)
    public Shipment() {}
    
    // getter / setter
}

// → 이제 JPA 가 Shipment 인식
// → shipmentRepository.findById(1L) 가능
// → 자동 SQL 생성

1.5 자기 점검 답변

@Entity 어노테이션의 의미는?

:
1. 선언:

  • JPA 관리 객체
  1. 위치:

    • 클래스 레벨
  2. 패키지:

    • jakarta.persistence
  3. 효과:

    • 매핑 / 캐시 / Dirty Checking

2️⃣ 엔티티 의미

2.1 엔티티 (Entity)

엔티티 (Entity):

  "@Entity 가 붙은 객체":
    - 영속성 컨텍스트가 관리
    - DB 행과 1:1 매핑
    - 식별자 (PK) 보유

2.2 도메인 객체

도메인 객체:

  엔티티 = 비즈니스 도메인의 핵심 객체:
    - 주문, 상품, 회원, 배송
    - 의미 있는 단위
    - 식별 (id) 가능

→ DDD 의 Entity 와 유사 (정확히 같진 않음)

2.3 영속성

영속성 (Persistence):

  엔티티의 특성:
    - DB 에 영구 저장
    - JVM 종료 후에도
    - 다시 가져올 수 있음

  영속성 컨텍스트:
    - 엔티티를 관리하는 1차 캐시
    - 트랜잭션 범위

2.4 엔티티 vs 일반 객체

항목일반 객체엔티티
@EntityXO
DB 매핑XO
영속성 컨텍스트XO
식별자 (PK)XO
변경 감지XO
생명 주기new/GC비영속/영속/준영속/삭제

2.5 ILIC 의 맥락

// ILIC 의 엔티티들 (102 테이블 × @Entity)

@Entity
@Table(name = "shipments")
public class Shipment { /* ... */ }

@Entity
@Table(name = "customers")
public class Customer { /* ... */ }

@Entity
@Table(name = "bookings")
public class Booking { /* ... */ }

@Entity
@Table(name = "ports")
public class Port { /* ... */ }

@Entity
@Table(name = "freights")
public class Freight { /* ... */ }

// ... 102 개

// 각 엔티티:
// - 도메인 의미
// - DB 테이블 매핑
// - 영속성 관리

2.6 자기 점검 답변

엔티티 (Entity) 의 정의는?

:
1. 엔티티:

  • @Entity 가 붙은 객체
  1. 도메인:

    • 비즈니스 핵심
  2. 영속성:

    • DB 영구 저장
  3. 차이:

    • 일반 객체 vs 엔티티

3️⃣ 3가지 조건

3.1 3가지 조건

엔티티의 3가지 조건:

  1. @Entity 어노테이션 (필수)
  
  2. 기본 생성자 (no-arg) 필수
     - JPA 가 리플렉션으로 객체 생성
     
  3. final 클래스 / final 메서드 X
     - JPA 가 프록시 (자식 클래스) 만들어야
     - final 은 상속 불가

→ 빠뜨리면 동작 X

3.2 + 권장 조건

+ 권장 조건:

  4. @Id 필드 (PK)
     - 모든 엔티티 식별자 필요

  5. 기본 생성자 protected 이상
     - public 권장
     - private 안 됨

  6. equals / hashCode 정의 (선택)
     - id 기반 비교
     - 컬렉션 사용 시

3.3 누락 시 에러

누락 시 에러:

  @Entity 없음:
    - JPA 인식 X
    - Repository 매핑 실패

  기본 생성자 없음:
    - InstantiationException
    - HibernateException

  final 클래스:
    - 프록시 생성 실패
    - Lazy Loading 시 에러

3.4 좋은 엔티티 예시

import jakarta.persistence.*;

@Entity
@Table(name = "shipments")
public class Shipment {
    
    @Id
    @GeneratedValue
    private Long id;
    
    private String blNo;
    private String status;
    
    // ① 기본 생성자 (필수) — protected 권장
    protected Shipment() {}
    
    // ② 커스텀 생성자 (선택)
    public Shipment(String blNo, String status) {
        this.blNo = blNo;
        this.status = status;
    }
    
    // ③ getter
    public Long getId() { return id; }
    public String getBlNo() { return blNo; }
    public String getStatus() { return status; }
    
    // setter (의도된 변경만)
    public void setStatus(String status) {
        this.status = status;
    }
    
    // ④ equals / hashCode (id 기반)
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Shipment)) return false;
        Shipment that = (Shipment) o;
        return id != null && id.equals(that.id);
    }
    
    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
}
// ⑤ final 아님 (프록시 가능)

3.5 ILIC 의 맥락

// ILIC 의 엔티티 표준 (Lombok 활용)
import lombok.*;
import jakarta.persistence.*;

@Entity
@Table(name = "shipments")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)   // 기본 생성자
@AllArgsConstructor
@Builder
public class Shipment {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "bl_no", length = 50, unique = true)
    private String blNo;
    
    @Column(length = 20)
    private String status;
    
    @Column(precision = 10, scale = 2)
    private BigDecimal weight;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private Customer customer;
    
    // 비즈니스 메서드
    public void markAsShipped() {
        if (!"BOOKED".equals(this.status)) {
            throw new IllegalStateException();
        }
        this.status = "SHIPPED";
    }
}
// → @Entity + @NoArgsConstructor (기본 생성자)
// → final X (Lombok 도 final 안 만듦)
// → 3가지 조건 충족
class Customer {}

3.6 자기 점검 답변

엔티티의 3가지 조건은?

:
1. @Entity:

  • 어노테이션
  1. 기본 생성자:

    • 필수
  2. final X:

    • 프록시
  3. + @Id:

    • 권장

4️⃣ 기본 생성자 필수

4.1 왜 필요한가

기본 생성자 (no-arg constructor):

  필요 이유:
    - JPA / Hibernate 가 리플렉션으로 객체 생성
    - newInstance() 호출 (기본 생성자)
    - 그 후 필드 값 채움 (리플렉션)

→ 없으면 객체 생성 불가

4.2 리플렉션 동작 (개념)

// JPA 내부 (개념적 의사 코드)
Class<Shipment> clazz = Shipment.class;
Shipment instance = clazz.getDeclaredConstructor().newInstance();
// ↑ 기본 생성자 필요

// 필드 채우기 (리플렉션)
Field idField = clazz.getDeclaredField("id");
idField.setAccessible(true);
idField.set(instance, 1L);
// ... 모든 필드

// → 기본 생성자 필수
class Shipment {}

4.3 누락 시 에러

누락 시 에러:

  No default constructor for entity:
  com.ilic.Shipment
  
  → HibernateException
  → 객체 생성 실패
  → 런타임 에러

4.4 접근 제어자

// 접근 제어자
public Shipment() {}        // ✅ 가장 안전
protected Shipment() {}      // ✅ 권장 (외부 호출 방지)
Shipment() {}                // ⚠️ package-private (괜찮음)
private Shipment() {}        // ❌ JPA 가 접근 못 함

// 권장:
// - protected (외부에서 무분별 사용 방지)
// - 필요 시 Builder 또는 정적 팩토리 메서드

4.5 Lombok 활용

// Lombok 으로 간편하게
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class Shipment {
    // 자동 생성:
    // protected Shipment() {}
    // public Shipment(필드 모두) {}
}

// 또는 @Data, @Builder 등 다른 어노테이션
class Shipment {}

4.6 ILIC 의 맥락

// ILIC 의 기본 생성자 패턴
@Entity
@Table(name = "shipments")
@NoArgsConstructor(access = AccessLevel.PROTECTED)  // ① 기본 생성자 (JPA)
@AllArgsConstructor(access = AccessLevel.PRIVATE)   // ② 전체 생성자 (Builder)
@Builder
@Getter
public class Shipment {
    @Id @GeneratedValue
    private Long id;
    
    private String blNo;
    private String status;
    
    // 정적 팩토리 (도메인 의미)
    public static Shipment create(String blNo) {
        return Shipment.builder()
            .blNo(blNo)
            .status("BOOKED")
            .build();
    }
    
    // 비즈니스 메서드
    public void markAsShipped() {
        this.status = "SHIPPED";
    }
}

// 사용
// 외부 코드:
// new Shipment() ← ❌ protected, 외부 X
// Shipment.create("BL001") ← ✅ 정적 팩토리

// JPA:
// 리플렉션으로 protected Shipment() 호출 (가능)
// 그 후 필드 채움

4.7 자기 점검 답변

기본 생성자 필수인 이유는?

:
1. 이유:

  • 리플렉션 객체 생성
  1. 동작:

    • newInstance() + 필드 채움
  2. 접근 제어:

    • protected 권장
  3. Lombok:

    • @NoArgsConstructor

5️⃣ final 클래스 X (프록시)

5.1 왜 프록시?

JPA 가 프록시 만드는 이유:

  1. Lazy Loading:
     - @ManyToOne(fetch = LAZY)
     - 실제 접근 시점에 SQL
     - 프록시 객체 먼저 (값 X)

  2. 변경 감지 (일부):
     - 객체 수정 감지
     - 트랜잭션 commit 시 UPDATE

→ 프록시 필요

5.2 프록시의 동작

프록시의 동작:

  엔티티 클래스 (Shipment):
       ↑ 상속
  Hibernate 가 동적 생성 자식 (Shipment$$ProxyXX):
    - Shipment 의 메서드 오버라이드
    - getId() 호출 시: 캐시 사용
    - 다른 메서드 호출 시: SQL 실행 + 위임

→ Hibernate 가 런타임에 자식 만듦

5.3 final 의 문제

final 의 문제:

  final class Shipment {  // ❌
      // ...
  }
  
  Hibernate:
    - Shipment 의 자식 만들려고 함
    - 하지만 final → 상속 불가
    - 프록시 생성 실패
    - 에러

5.4 final 메서드도 X

// final 메서드도 안 됨
@Entity
public class Shipment {
    @Id
    private Long id;
    
    public final Long getId() {   // ❌
        return id;
    }
    // 프록시가 오버라이드 못 함
    // Lazy Loading 등 동작 X
}

// 권장:
// - 클래스 final X
// - 메서드 final X
// - 또는 Lombok @Getter (final 안 붙임)

5.5 프록시 생성 라이브러리

프록시 생성 라이브러리:

  Hibernate 가 사용:
    - CGLIB (또는 ByteBuddy)
    - 런타임 바이트코드 생성
    - 자식 클래스 (Subclass) 생성

  → 부모 (엔티티) 의 final 은 X

5.6 Lazy Loading 예시

// Lazy Loading 동작 (final X 일 때)
@Entity
public class Shipment {
    @ManyToOne(fetch = FetchType.LAZY)
    private Customer customer;   // 프록시
}

Shipment s = repo.findById(1L).orElseThrow();
// s.customer 는 프록시 객체 (실제 Customer X)
// 클래스: Customer$$ProxyXX

s.getCustomer().getName();   // 이때 SQL 실행 (실제 로딩)

// Customer 가 final 이면?
// → 프록시 생성 실패
// → LazyInitializationException 같은 문제
class Customer {}
class Shipment { Customer getCustomer() { return null; } }
ShipmentRepository repo;
interface ShipmentRepository { java.util.Optional<Shipment> findById(Long id); }

5.7 ILIC 의 맥락

// ILIC 의 엔티티 (final 절대 X)
@Entity
@Table(name = "shipments")
public class Shipment {   // final X
    // 필드 / 메서드 모두 final X
}

@Entity
@Table(name = "customers")
public class Customer {   // final X
    // ...
}

// Lazy Loading 동작:
@Entity
public class Shipment {
    @ManyToOne(fetch = FetchType.LAZY)
    private Customer customer;   // Customer$$ProxyXX
}

Shipment s = repo.findById(1L).orElseThrow();
s.getCustomer().getName();   // 이 시점 SQL

// → final 없이 모든 엔티티 정상 동작
// → 만약 final 이면 모든 ILIC 동작 멈춤
ShipmentRepository repo;
class ShipmentRepository {
    java.util.Optional<Shipment> findById(Long id) { return null; }
}

5.8 자기 점검 답변

final 클래스 X 인 이유 (프록시) 는?

:
1. 프록시:

  • Hibernate 가 자식 생성
  1. 목적:

    • Lazy Loading 등
  2. final:

    • 상속 불가 → 프록시 X
  3. CGLIB:

    • 바이트코드 자식 생성

6️⃣ DTO vs Entity

6.1 DTO

DTO (Data Transfer Object):

  데이터 전달 전용 객체:
    - 계층 간 (Controller ↔ Service)
    - 외부 API 응답
    - 비즈니스 로직 X
    - 영속성 X

6.2 Entity

Entity:

  영속성 객체:
    - DB 와 매핑
    - 영속성 컨텍스트 관리
    - 도메인 로직 가능
    - 변경 감지

6.3 비교 표

항목DTOEntity
목적데이터 전달영속성 + 도메인
@EntityXO
DB 매핑XO
영속성 컨텍스트XO
비즈니스 로직X (보통)O
변경 감지XO
Lifecycle단순4가지 상태
변경 가능성자유신중

6.4 분리의 가치

분리의 가치:

  Entity 만 사용 시:
    - 노출 위험 (필드 모두)
    - 변경 시 영향 (API 까지)
    - 보안 위험 (id 등)

  Entity + DTO:
    - 계층 분리 (SoC)
    - 안전 (필드 노출 제한)
    - 변경 격리

→ 5주차 SoC 원칙

6.5 DTO 변환 코드

// DTO 정의
public class ShipmentDto {
    private Long id;
    private String blNo;
    private String customerName;
    
    public ShipmentDto(Shipment s) {
        this.id = s.getId();
        this.blNo = s.getBlNo();
        this.customerName = s.getCustomer().getName();
    }
}

// 변환
ShipmentDto dto = new ShipmentDto(shipment);
// 또는 MapStruct (자동 매핑)
class Shipment {
    Long getId() { return null; }
    String getBlNo() { return null; }
    Customer getCustomer() { return null; }
}
class Customer { String getName() { return null; } }
Shipment shipment;

6.6 Projection (Spring Data JPA)

// Projection (자동 DTO)
public interface ShipmentProjection {
    Long getId();
    String getBlNo();
    String getCustomerName();
}

// Repository
public interface ShipmentRepository extends JpaRepository<Shipment, Long> {
    List<ShipmentProjection> findByStatus(String status);
}
// → Entity 가 아닌 Projection 반환
// → 필요 컬럼만
class Shipment {}
interface JpaRepository<T, ID> {}

6.7 ILIC 의 맥락

// ILIC 의 Entity + DTO 분리

// Entity (영속성)
@Entity
@Table(name = "shipments")
public class Shipment {
    @Id @GeneratedValue
    private Long id;
    private String blNo;
    private String status;
    private BigDecimal weight;
    
    @ManyToOne
    private Customer customer;
    
    // 비즈니스 메서드
    public void markAsShipped() { this.status = "SHIPPED"; }
}

// DTO (API 응답)
public record ShipmentResponseDto(
    Long id,
    String blNo,
    String status,
    BigDecimal weight,
    String customerName
) {
    public static ShipmentResponseDto from(Shipment s) {
        return new ShipmentResponseDto(
            s.getId(),
            s.getBlNo(),
            s.getStatus(),
            s.getWeight(),
            s.getCustomer().getName()
        );
    }
}

// DTO (요청)
public record ShipmentCreateRequest(
    @NotBlank String blNo,
    @NotNull BigDecimal weight
) {
    public Shipment toEntity(Customer customer) {
        return Shipment.builder()
            .blNo(blNo)
            .weight(weight)
            .customer(customer)
            .status("BOOKED")
            .build();
    }
}

// Controller
@RestController
public class ShipmentController {
    @GetMapping("/shipments/{id}")
    public ShipmentResponseDto get(@PathVariable Long id) {
        Shipment s = service.get(id);
        return ShipmentResponseDto.from(s);
    }
}

// → Entity (내부) / DTO (외부) 분리
// → SoC, 안전
class Customer { String getName() { return null; } }
class Shipment {
    Long getId() { return null; }
    String getBlNo() { return null; }
    String getStatus() { return null; }
    java.math.BigDecimal getWeight() { return null; }
    Customer getCustomer() { return null; }
    static Builder builder() { return null; }
    static class Builder {
        Builder blNo(String s) { return this; }
        Builder weight(java.math.BigDecimal w) { return this; }
        Builder customer(Customer c) { return this; }
        Builder status(String s) { return this; }
        Shipment build() { return null; }
    }
}
class ShipmentService { Shipment get(Long id) { return null; } }
@interface NotBlank {}
@interface NotNull {}

6.8 자기 점검 답변

DTO 와 Entity 의 차이는?

:
1. DTO:

  • 데이터 전달
  1. Entity:

    • 영속성
  2. 차이:

    • @Entity / 영속성 컨텍스트
  3. 권장:

    • 분리

7️⃣ @Table

7.1 @Table

@Table:

  엔티티가 매핑될 테이블 명시:
    - 테이블명 (name)
    - 스키마 / 카탈로그
    - 유니크 제약
    - 인덱스

7.2 사용

import jakarta.persistence.*;

@Entity
@Table(
    name = "shipments",                              // 테이블명
    schema = "ilic_db",                              // 스키마 (선택)
    uniqueConstraints = {
        @UniqueConstraint(name = "uk_bl_no", 
                          columnNames = "bl_no")
    },
    indexes = {
        @Index(name = "idx_status", columnList = "status"),
        @Index(name = "idx_customer", columnList = "customer_id")
    }
)
public class Shipment {
    // ...
}

7.3 옵션

@Table 옵션:

  name: 테이블명
  schema: 스키마
  catalog: 카탈로그
  uniqueConstraints: 복합 unique
  indexes: 인덱스 (DDL 생성 시)

7.4 ddl-auto 와 함께

# application.yml
spring:
  jpa:
    hibernate:
      ddl-auto: update   # 또는 create / validate
ddl-auto:

  none: 아무것도 X (운영)
  validate: 매핑 검증만
  update: 변경 사항 반영 (개발)
  create: 매번 새로 (테스트)
  create-drop: 시작 시 create, 종료 시 drop

  @Table 의 indexes / uniqueConstraints:
    - DDL 생성 시 반영
    - 운영은 보통 Flyway / Liquibase

7.5 ILIC 의 맥락

// ILIC 의 @Table 활용
@Entity
@Table(
    name = "shipments",
    indexes = {
        @Index(name = "idx_shipments_status", columnList = "status"),
        @Index(name = "idx_shipments_customer", columnList = "customer_id"),
        @Index(name = "idx_shipments_created", columnList = "created_at")
    },
    uniqueConstraints = {
        @UniqueConstraint(name = "uk_shipments_bl_no", columnNames = "bl_no")
    }
)
public class Shipment {
    // ...
}

// 효과:
// - DDL 자동 생성 (개발)
// - 인덱스 명시 (성능)
// - UNIQUE 제약 (데이터 무결성)

// ILIC 운영:
// - Flyway / Liquibase 로 마이그레이션
// - @Table 의 메타정보는 참고
// - 실제 DDL 은 별도 관리

7.6 자기 점검 답변

@Table 어노테이션은?

:
1. 목적:

  • 테이블 매핑 명시
  1. 옵션:

    • name / schema / indexes
  2. DDL:

    • ddl-auto 와 함께
  3. 운영:

    • Flyway 와 병행

8️⃣ 이름 명시 vs 자동

8.1 자동 이름 변환

자동 이름 변환:

  Spring Boot 의 기본 명명 전략:
    - SpringPhysicalNamingStrategy

  변환:
    - Shipment → shipments (snake_case, 복수)
    - 사실은 (Hibernate 기본):
      - Shipment → shipment

  Spring Boot 의 추가:
    - 단수 → 그대로 (Shipment → shipment)
    - 또는 명시

8.2 클래스명 → 테이블명

// @Table 생략 시
@Entity
public class Shipment {
    // 자동: shipment (단수)
}

// 명시
@Entity
@Table(name = "shipments")
public class Shipment {
    // 명시: shipments (복수)
}

// 권장: 명시

8.3 camelCase → snake_case

camelCase → snake_case:

  자바 필드:
    - blNo
    - createdAt
    - shipperName

  DB 컬럼 (자동):
    - bl_no
    - created_at
    - shipper_name

→ Spring Boot 자동

8.4 명명 전략 변경

# application.yml
spring:
  jpa:
    hibernate:
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
        # 기본: SpringPhysicalNamingStrategy

  # 또는 커스텀

8.5 명시의 가치

명시의 가치:

  @Table(name = ...) 권장:
    - 명확성
    - 변경 안전
    - 자동 규칙 의존 X

  @Column 도 마찬가지

→ 보통 명시

8.6 ILIC 의 맥락

// ILIC 의 명명 (명시 vs 자동)

// 명시 (권장)
@Entity
@Table(name = "shipments")
public class Shipment {
    @Column(name = "bl_no", length = 50)
    private String blNo;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
}

// 자동 (Spring Boot 기본 명명)
@Entity
public class Shipment {
    private String blNo;        // bl_no (자동)
    private LocalDateTime createdAt;   // created_at (자동)
}

// ILIC 표준:
// - @Table(name = ...) 명시 (테이블명)
// - @Column 은 필요 시만 명시
//   (자동 변환 잘 되는 경우 생략)
// - 특수 케이스 (이름 다름) 만 명시

8.7 자기 점검 답변

이름 명시 vs 자동은?

:
1. 자동:

  • Spring Boot 기본 명명
  1. 변환:

    • camelCase → snake_case
  2. 권장:

    • @Table 명시
  3. @Column:

    • 필요 시만

9️⃣ 엔티티 라이프사이클

9.1 4가지 상태

엔티티 4가지 상태:

  1. 비영속 (new / transient):
     - new Shipment() 직후
     - 영속성 컨텍스트 X
     - DB X

  2. 영속 (managed / persistent):
     - em.persist() 후
     - 영속성 컨텍스트 관리
     - Dirty Checking 대상

  3. 준영속 (detached):
     - em.detach() 또는 close 후
     - 영속성 컨텍스트 X
     - 변경 감지 X

  4. 삭제 (removed):
     - em.remove() 후
     - 영속성 컨텍스트에 삭제 표시
     - flush 시 DELETE

9.2 상태 전이

상태 전이:

  new Shipment()
      ↓ (객체 생성)
  비영속 (new)
      ↓ em.persist()
  영속 (managed)
      ↓ em.detach() / em.clear()
  준영속 (detached)
      ↓ em.merge()
  영속 (managed)
      ↓ em.remove()
  삭제 (removed)
      ↓ flush
  DELETE 실행

9.3 코드 예시

// 1. 비영속
Shipment s = new Shipment();
s.setBlNo("BL001");

// 2. 영속
em.persist(s);
// 영속성 컨텍스트 등록
// INSERT 는 아직 X (flush 시)

s.setStatus("SHIPPED");
// 영속이므로 변경 감지 자동
// commit 시 UPDATE

// 3. 준영속
em.detach(s);
// 또는 em.clear() (모두)
// 또는 트랜잭션 종료
s.setStatus("CANCELLED");
// 변경 감지 X (DB 반영 X)

// 4. 다시 영속
Shipment merged = em.merge(s);
// 새 영속 객체 반환

// 5. 삭제
em.remove(merged);
// 영속성 컨텍스트에 삭제 표시
// flush 시 DELETE
class Shipment {
    void setBlNo(String s) {}
    void setStatus(String s) {}
}
class EntityManager {
    void persist(Object o) {}
    void detach(Object o) {}
    void clear() {}
    <T> T merge(T t) { return null; }
    void remove(Object o) {}
}
EntityManager em;

9.4 Spring Data JPA

Spring Data JPA 의 추상화:

  save() = persist 또는 merge
    - id null = persist (new)
    - id 있음 = merge (또는 그냥 영속)

  findById() = find
    - 영속 상태로 반환

  delete() = remove

→ 라이프사이클 추상화

9.5 ILIC 의 맥락

// ILIC 의 엔티티 라이프사이클

@Service
@Transactional
public class ShipmentService {
    @Autowired ShipmentRepository repo;
    
    public Shipment create(ShipmentCreateRequest req) {
        // 1. 비영속
        Shipment s = Shipment.builder()
            .blNo(req.blNo())
            .build();
        
        // 2. 영속
        return repo.save(s);   // INSERT
    }
    
    public void updateStatus(Long id, String newStatus) {
        // 영속 상태 (find)
        Shipment s = repo.findById(id).orElseThrow();
        
        // 변경 감지 (자동 UPDATE)
        s.setStatus(newStatus);
        // 메서드 종료 → 트랜잭션 commit → UPDATE 자동
    }
    
    public void cancel(Long id) {
        Shipment s = repo.findById(id).orElseThrow();
        
        // 삭제
        repo.delete(s);   // DELETE (flush 시)
    }
}

// → 영속성 컨텍스트가 라이프사이클 관리
// → 개발자는 객체 작업만
class Shipment { 
    void setStatus(String s) {}
    static Builder builder() { return null; }
    static class Builder {
        Builder blNo(String s) { return this; }
        Shipment build() { return null; }
    }
}
record ShipmentCreateRequest(String blNo) {}
ShipmentRepository repo;
interface ShipmentRepository {
    Shipment save(Shipment s);
    java.util.Optional<Shipment> findById(Long id);
    void delete(Shipment s);
}

9.6 면접 단골 질문 매핑

Q핵심 답변
@Entity?JPA 관리 선언
엔티티?영속성 객체
3가지 조건?@Entity + no-arg + final X
기본 생성자?리플렉션
final X?프록시
DTO vs Entity?전달 vs 영속성
@Table?테이블 매핑
자동 명명?snake_case
라이프사이클?4가지 상태
Lazy Loading?프록시

9.7 자기 점검 체크리스트

@Entity

  • 정의

엔티티

  • 의미

3가지 조건

  • 충족

기본 생성자

  • 리플렉션

final X

  • 프록시

DTO vs Entity

  • 분리

@Table

  • 옵션

명명

  • 자동/명시

라이프사이클

  • 4가지

9.8 추가 심화 질문

Q1: 엔티티의 equals / hashCode?

답:

  • id 기반 (PK)
  • new 직후 id null 주의
  • Set / Map 사용 시 중요
  • Hibernate 가이드 따르기

Q2: 엔티티 vs 값 객체 (Value Object)?

답:

  • 엔티티: 식별자 (id), 변경 가능
  • 값 객체: id X, 불변
  • @Embeddable (Phase 4 후속)

Q3: @MappedSuperclass?

답:

  • 추상 부모 클래스 (엔티티 X)
  • 공통 필드 정의
  • 자식이 엔티티
  • 상속 매핑 아님 (단순 공통)

Q4: 엔티티 클래스에 비즈니스 메서드 OK?

답:

  • OK (도메인 모델)
  • markAsShipped, calculate 등
  • 풍부한 도메인 (Rich Domain Model)
  • vs Anemic (빈약, getter/setter 만)

Q5: 엔티티에 Spring 빈 주입?

답:

  • 권장 X (엔티티는 POJO)
  • DI 어색
  • 필요 시 Domain Service 분리

🎯 핵심 요약 — 3줄 정리

1. @Entity = JPA 관리 선언

  • 자바 클래스를 영속성 객체 (엔티티) 로 선언
  • DB 테이블 매핑, 영속성 컨텍스트 관리, Dirty Checking 대상

2. 3가지 조건

  • (1) @Entity 어노테이션, (2) 기본 생성자 필수 (리플렉션 객체 생성), (3) final 클래스 / 메서드 X (프록시 자식 생성)
  • 권장: @Id 필드, public/protected 생성자, equals/hashCode

3. DTO vs Entity

  • DTO: 데이터 전달, 영속성 X, 자유
  • Entity: 영속성, DB 매핑, 변경 감지, 4가지 라이프사이클 (비영속/영속/준영속/삭제)
  • 실무: 분리 권장 (SoC)

📚 다음으로...

Unit 4.2 — @Id 와 PK 매핑

이번 Unit에서 엔티티의 개념을 봤다면, 다음은 @Id (PK 매핑).

  • @Id: 테이블의 PK 컬럼과 매핑
  • 모든 엔티티는 @Id 1개 (또는 복합 키)
  • 복합 키 (@IdClass, @EmbeddedId)
  • @Id 없으면?

Phase 4 진행 상황

🏷️ Phase 4 — JPA 엔티티 매핑
  ✅ Unit 4.1 @Entity 와 엔티티 개념 ← 여기
  ⏭ Unit 4.2 @Id 와 PK 매핑
  ⏭ Unit 4.3 @GeneratedValue 전략 ★깊이
  ⏭ Unit 4.4 @Column 과 컬럼 매핑
  ⏭ Unit 4.5 자동 매핑 규칙 (camelCase ↔ snake_case)

7주차 누적 진행

🗂️ Part A — 데이터 모델링과 ORM
  ✅ Phase 1 (5)
  ✅ Phase 2 (2)
  ✅ Phase 3 (4)
  🏷️ Phase 4 (1/5)

총: 12/24 Unit (50%!)

🏷️ Phase 4 시작 — JPA 엔티티 매핑

profile
Software Developer

0개의 댓글