F-LAB JAVA · 7주차 · Phase 4 · JPA 엔티티 매핑
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
@Id 는 엔티티의 식별자 (Primary Key) 필드를 표시하는 어노테이션으로 모든 엔티티가 반드시 1개 (또는 복합 키) 가져야 하며 JPA 가 영속성 컨텍스트에서 이 값을 기준으로 객체 동일성 (==) 을 보장하고, 복합 키는 @IdClass 또는 @EmbeddedId 로 표현하며 실무는 자연 키보다 대리 키 (Surrogate Key, 보통 Long auto-increment) 가 압도적이다.
@Id 는 엔티티의 식별자 (Primary Key) 필드를 표시 하는 어노테이션이다.
모든 엔티티는 반드시 @Id 1개 이상 을 가져야 하며 (단일 키 또는 복합 키), 누락 시 매핑 시점에No identifier specified for entity예외가 발생한다.
JPA 가 이 PK 값을 기준으로 — (1) DB 의 행 식별 (SELECT WHERE id = ?), (2) 영속성 컨텍스트 1차 캐시 키 (같은 id = 같은 객체,==보장), (3) 변경 감지 추적 단위 — 의 핵심 정보로 사용한다.
복합 키 (여러 컬럼이 합쳐 PK) 는 — @IdClass (별도 PK 클래스, 엔티티에 PK 필드 그대로) 또는 @EmbeddedId (PK 를 임베디드 객체로 감쌈) 의 두 방식으로 표현한다.
실무에선 대리 키 (Surrogate Key, 의미 없는 자동 생성 PK, 보통 Long auto-increment) 가 압도적 — 자연 키 (Natural Key, 의미 있는 PK 예: 주민번호) 는 변경 가능성·성능 등의 이유로 거의 안 쓰며, ILIC 의 모든 102 테이블도id BIGINT AUTO_INCREMENT대리 키 표준이다.
@Id = 주민등록번호:
비유:
- 엔티티 = 사람
- @Id = 주민등록번호 (PK)
- 모든 사람 = 유일 번호 보유
PK 의 역할:
- DB: 행 식별 (특정 사람 찾기)
- 객체: 같은 번호 = 같은 사람
- 영속성: 1차 캐시 키
복합 키 = 다중 식별:
- 학교 + 학년 + 반 + 번호 (학생 식별)
- 한 필드로 부족 시
- @IdClass / @EmbeddedId
자연 키 vs 대리 키:
- 자연 키: 의미 있는 (주민번호, 이메일)
- 대리 키: 의미 없는 (auto-increment 번호)
실무 = 대리 키:
- 변경 불가능 (id 1 은 영원히 같은 행)
- 인덱스 효율
- 외부 노출 안전
ILIC:
- 모든 102 테이블 = Long id
- bl_no 는 UNIQUE 컬럼 (자연 키 후보)
- 하지만 PK 는 id (대리 키)
→ @Id = PK 매핑, 반드시 1개, 영속성 핵심, 대리 키 압도적.
1. @Id 정의
2. PK 의 역할
3. @Id 필수 (1개)
4. @Id 없으면 에러
5. 단일 키 매핑
6. 복합 키 @IdClass
7. 복합 키 @EmbeddedId
8. 자연 키 vs 대리 키
9. PK 타입 선택 + equals/hashCode
@Id:
엔티티의 식별자 (Primary Key) 필드:
- 어노테이션 표시
- 필드 또는 메서드 위
- 모든 엔티티 1개 이상 필수
import jakarta.persistence.Id;
@Entity
public class Shipment {
@Id // ← 필드 위
private Long id;
// ...
}
// 또는 메서드 위 (Property Access)
@Entity
public class Shipment {
private Long id;
@Id
public Long getId() { return id; }
}
접근 방식:
필드 접근 (Field Access):
- @Id 가 필드 위
- JPA 가 리플렉션으로 필드 직접 접근
- 실무 표준
메서드 접근 (Property Access):
- @Id 가 getter 위
- getter 호출
- 거의 안 씀
→ 필드 접근 권장 (간결)
// @Id 표준 (ILIC)
@Entity
@Table(name = "shipments")
public class Shipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // PK
private String blNo;
private String status;
// ...
}
// 102 테이블 모두 비슷한 패턴:
// - @Id Long id
// - 자동 생성 (IDENTITY)
// - 필드 접근
class Customer {}
@Id 어노테이션의 의미는?
답:
1. @Id:
위치:
필수:
표준:
DB 에서 PK:
Primary Key:
- 유일한 식별자
- NOT NULL
- UNIQUE
- 자동 인덱스 (Clustered Index)
역할:
- 행 식별
- 빠른 조회
- FK 의 참조 대상
객체에서 식별자:
자바 객체 식별:
- == (메모리 주소)
- equals (내용)
JPA 엔티티:
- PK 값으로 식별
- 같은 PK = 같은 데이터
- 같은 트랜잭션 내 == (1차 캐시)
JPA 의 PK 활용:
1. SELECT (em.find):
- SELECT * FROM table WHERE id = ?
2. 영속성 컨텍스트 캐시:
- id 가 키
- 같은 id 두 번 find = SQL 1번 + 캐시 1번
3. UPDATE:
- WHERE id = ?
- 정확한 행만
4. DELETE:
- WHERE id = ?
// 1차 캐시 (영속성 컨텍스트)
@Transactional
public void demo() {
Shipment s1 = repo.findById(1L).orElseThrow(); // SQL 1번
Shipment s2 = repo.findById(1L).orElseThrow(); // 캐시 (SQL X)
// 같은 트랜잭션 안에서
s1 == s2; // true! (영속성 컨텍스트 1차 캐시)
s1.equals(s2); // true
// 다른 트랜잭션이면:
// s1 == s2; // false (다른 영속성 컨텍스트)
}
class Shipment {}
ShipmentRepository repo;
interface ShipmentRepository { java.util.Optional<Shipment> findById(Long id); }
// ILIC 의 PK 활용
@Transactional
public void process(Long shipmentId) {
// 1. PK 로 조회 (영속성 컨텍스트 시작)
Shipment s = shipmentRepository.findById(shipmentId).orElseThrow();
// 2. 같은 트랜잭션 내 다시 조회 (캐시)
Shipment same = shipmentRepository.findById(shipmentId).orElseThrow();
s == same; // true (같은 객체)
// 3. 수정 (PK 기반)
s.setStatus("SHIPPED");
// 트랜잭션 commit 시:
// UPDATE shipments SET status = ? WHERE id = ?
// ↑ PK 사용
// 4. 삭제
shipmentRepository.delete(s);
// DELETE FROM shipments WHERE id = ?
// ↑ PK 사용
}
class Shipment { void setStatus(String s) {} }
ShipmentRepository shipmentRepository;
interface ShipmentRepository {
java.util.Optional<Shipment> findById(Long id);
void delete(Shipment s);
}
PK 의 역할 (DB / 객체) 은?
답:
1. DB:
객체:
JPA:
활용:
모든 엔티티 @Id 필수:
- 단일 키: @Id 1개
- 복합 키: @Id 여러 (또는 @EmbeddedId)
- 0개: 금지 (예외)
→ JPA 의 핵심 규칙
왜 필수인가:
JPA 는 PK 로:
- 행 식별
- 1차 캐시 키
- 변경 감지
PK 없으면:
- 식별 불가
- 캐시 X
- 어떤 행 UPDATE?
→ 동작 불가
// 단일 키 (99% 케이스)
@Entity
public class Shipment {
@Id
private Long id;
// ...
}
// 1개 PK 필드
// 복합 키 (드물게)
@Entity
@IdClass(ShipmentLegId.class)
public class ShipmentLeg {
@Id
private Long shipmentId;
@Id
private int legSequence;
// 두 필드 합쳐 PK
}
public class ShipmentLegId implements Serializable {
private Long shipmentId;
private int legSequence;
// equals / hashCode 필수
}
// ILIC = 거의 모두 단일 키
@Entity
public class Shipment {
@Id
@GeneratedValue
private Long id;
}
@Entity
public class Customer {
@Id
@GeneratedValue
private Long id;
}
@Entity
public class Booking {
@Id
@GeneratedValue
private Long id;
}
// 102 테이블 모두 @Id Long id
// 복합 키 예외 (드물게):
// - ShipmentItem 같은 종속 엔티티
// - 또는 history 테이블
// - 그래도 보통 별도 id 추가
@Id 필수인 이유는?
답:
1. 필수:
이유:
개수:
표준:
에러 발생 시점:
애플리케이션 시작 시:
- Spring Boot 가 엔티티 스캔
- @Entity 인데 @Id 없음
- 즉시 예외
→ 런타임 X, 시작 시 발견
에러 메시지 (예):
org.hibernate.AnnotationException:
No identifier specified for entity:
com.ilic.Shipment
또는:
Caused by: org.hibernate.MappingException:
...
→ "PK 가 없습니다"
동작 안 됨:
- Repository 생성 실패
- 애플리케이션 시작 실패
- 빨리 발견 (좋음)
→ 컴파일 시점은 아니지만, 빠른 실패
// ❌ @Id 없음
@Entity
public class Shipment {
private Long id; // @Id 없음
private String blNo;
}
// 실행 시:
// org.hibernate.AnnotationException:
// No identifier specified for entity: com.ilic.Shipment
// → 시작 실패
// ✅ @Id 있음
@Entity
public class Shipment {
@Id
private Long id;
private String blNo;
}
IDE 도움:
IntelliJ + JPA Buddy / 또는 자체:
- @Entity 인데 @Id 없으면 경고
- 빨간 줄
- 컴파일 전 발견
→ 개발 시점 발견
// ILIC 의 @Id 누락 방지
// IDE 경고 + 시작 시 에러
// → 102 테이블 모두 @Id 확인
// 또한 베이스 클래스로 보장
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
}
@Entity
public class Shipment extends BaseEntity {
// @Id 상속받음
// 누락 방지 + 표준화
}
@Id 없으면 어떻게 되는가?
답:
1. 시점:
에러:
결과:
방지:
단일 키 (실무 표준):
하나의 컬럼이 PK:
- 단순
- 빠름
- 99% 케이스
보통:
- Long (auto-increment)
- 또는 UUID (분산)
// 표준 패턴
@Entity
public class Shipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ...
}
// DB:
// CREATE TABLE shipments (
// id BIGINT AUTO_INCREMENT PRIMARY KEY,
// ...
// );
| 타입 | 사용 케이스 | 장단점 |
|---|---|---|
| Long | 일반 (auto-increment) | 빠름, 작음, 표준 |
| UUID | 분산 시스템 | 충돌 X, 큼 (128bit) |
| String | 비즈니스 키 | 자연 키 시, 보통 X |
| Integer | 작은 테이블 | 21억 한계 |
// Long (Wrapper) vs long (primitive)
@Id
private Long id; // ✅ Wrapper 권장
// 이유:
// - new 직후 null (비영속 상태 구분)
// - long 이면 0 으로 초기화 → 헷갈림
@Id
private long id; // ❌ 비권장
// long 은 null X
// new 직후 0 → 이미 영속처럼 보임
@Id + @GeneratedValue:
거의 항상 함께:
@Id
@GeneratedValue(strategy = ...)
private Long id;
GeneratedValue:
- 자동 생성 전략
- IDENTITY / SEQUENCE / TABLE / AUTO
- 다음 Unit 4.3 ★ 깊이
→ 짝꿍
// ILIC 의 단일 키 표준
@Entity
@Table(name = "shipments")
public class Shipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ...
}
@Entity
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ...
}
// 102 테이블 동일
// - @Id Long id
// - IDENTITY (MySQL auto-increment)
// DB:
// CREATE TABLE shipments (
// id BIGINT AUTO_INCREMENT PRIMARY KEY,
// ...
// );
// 장점:
// - 단순
// - 일관
// - 빠름
// - 분산 X (단일 DB)
단일 키 매핑은?
답:
1. 단일 키:
타입:
Wrapper:
GeneratedValue:
복합 키:
여러 컬럼이 합쳐 PK:
- 단일 컬럼으론 식별 X
- 두 개 이상의 조합으로
예:
- 학생 (학교 + 학번)
- 주문 상품 (주문ID + 상품ID)
드물게 사용
// 1. PK 클래스 (별도)
public class ShipmentLegId implements Serializable {
private Long shipmentId;
private int legSequence;
// 기본 생성자, equals, hashCode 필수
public ShipmentLegId() {}
public ShipmentLegId(Long shipmentId, int legSequence) {
this.shipmentId = shipmentId;
this.legSequence = legSequence;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ShipmentLegId)) return false;
ShipmentLegId that = (ShipmentLegId) o;
return shipmentId.equals(that.shipmentId)
&& legSequence == that.legSequence;
}
@Override
public int hashCode() {
return Objects.hash(shipmentId, legSequence);
}
}
// 2. 엔티티
@Entity
@IdClass(ShipmentLegId.class)
public class ShipmentLeg {
@Id
@Column(name = "shipment_id")
private Long shipmentId;
@Id
@Column(name = "leg_sequence")
private int legSequence;
private String portOfLoading;
// ...
}
// 조회
ShipmentLegId pk = new ShipmentLegId(1L, 1);
ShipmentLeg leg = em.find(ShipmentLeg.class, pk);
// 또는 Repository
public interface ShipmentLegRepository
extends JpaRepository<ShipmentLeg, ShipmentLegId> {
// 두 번째 타입이 PK 클래스
}
class ShipmentLegId {}
class ShipmentLeg {}
class EntityManager {
<T> T find(Class<T> c, Object id) { return null; }
}
EntityManager em;
interface JpaRepository<T, ID> {}
@IdClass 의 특징:
장점:
- SQL 친화 (필드명 그대로)
- 단순
단점:
- PK 필드 중복 (엔티티 + PK 클래스)
- equals/hashCode 직접 작성
- 코드 ↑
// ILIC 의 복합 키 활용 (드물게)
// 시나리오: shipment_status_history
// 같은 shipment 의 여러 상태 변경 이력
// PK: (shipment_id, sequence)
public class ShipmentStatusHistoryId implements Serializable {
private Long shipmentId;
private int sequence;
// 기본 생성자, equals, hashCode
// ... (Lombok @EqualsAndHashCode 가능)
}
@Entity
@Table(name = "shipment_status_history")
@IdClass(ShipmentStatusHistoryId.class)
public class ShipmentStatusHistory {
@Id
@Column(name = "shipment_id")
private Long shipmentId;
@Id
@Column(name = "sequence")
private int sequence;
@Column(name = "status")
private String status;
@Column(name = "changed_at")
private LocalDateTime changedAt;
}
// 하지만 ILIC 표준은:
// - 별도 id (auto-increment) + index
// - 복합 키 피함
@Entity
@Table(name = "shipment_status_history")
public class ShipmentStatusHistory {
@Id
@GeneratedValue
private Long id; // 대리 키
private Long shipmentId;
private int sequence;
private String status;
private LocalDateTime changedAt;
// unique 제약 (shipment_id, sequence) 로 보완
}
// → 단순 + 일관
복합 키 @IdClass 는?
답:
1. 복합 키:
@IdClass:
요구사항:
단점:
// 1. PK 임베디드 클래스
@Embeddable
public class ShipmentLegId implements Serializable {
@Column(name = "shipment_id")
private Long shipmentId;
@Column(name = "leg_sequence")
private int legSequence;
// 기본 생성자, equals, hashCode 필수
public ShipmentLegId() {}
public ShipmentLegId(Long shipmentId, int legSequence) {
this.shipmentId = shipmentId;
this.legSequence = legSequence;
}
// equals, hashCode
}
// 2. 엔티티
@Entity
public class ShipmentLeg {
@EmbeddedId
private ShipmentLegId id; // PK 객체로 임베드
private String portOfLoading;
// ...
}
// 조회
ShipmentLegId pk = new ShipmentLegId(1L, 1);
ShipmentLeg leg = em.find(ShipmentLeg.class, pk);
// PK 접근
Long shipmentId = leg.getId().getShipmentId();
int seq = leg.getId().getLegSequence();
class ShipmentLeg {
ShipmentLegId getId() { return null; }
}
class ShipmentLegId {
Long getShipmentId() { return null; }
int getLegSequence() { return 0; }
}
EntityManager em;
class EntityManager {
<T> T find(Class<T> c, Object id) { return null; }
}
| 항목 | @IdClass | @EmbeddedId |
|---|---|---|
| PK 클래스 | 필요 | 필요 (@Embeddable) |
| 엔티티 필드 | PK 필드 직접 | PK 객체 (id) |
| SQL 친화 | O | △ |
| 객체지향 | △ | O |
| 코드량 | 더 많음 | 적음 |
어떤 걸 사용?:
@EmbeddedId 권장:
- 더 객체지향
- PK 클래스 활용
- JPA 표준
@IdClass:
- SQL 친화 (필드명 그대로)
- 옛 스타일
→ 신규는 @EmbeddedId 권장
권장: 복합 키 회피
실무 권장:
- 복합 키 대신 대리 키 (auto-increment id)
- UNIQUE 제약 (자연 키 조합)
이유:
- 단순 (코드 적음)
- JPA 연관 매핑 간단
- 표준화
→ 복합 키 회피
// ILIC 의 복합 키 회피 패턴
// ❌ 복합 키 (피함)
@Entity
public class ShipmentItem {
@EmbeddedId
private ShipmentItemId id; // (shipment_id, item_seq)
private String itemName;
}
// ✅ 대리 키 + UNIQUE (ILIC 표준)
@Entity
@Table(name = "shipment_items",
uniqueConstraints = @UniqueConstraint(
name = "uk_shipment_item_seq",
columnNames = {"shipment_id", "item_sequence"}
)
)
public class ShipmentItem {
@Id
@GeneratedValue
private Long id; // 대리 키
@ManyToOne
@JoinColumn(name = "shipment_id")
private Shipment shipment;
@Column(name = "item_sequence")
private int itemSequence;
private String itemName;
}
// 장점:
// - JPA 연관 매핑 간단
// - 외부 노출 안전 (id 만)
// - 102 테이블 일관
class Shipment {}
복합 키 @EmbeddedId 는?
답:
1. @EmbeddedId:
@Embeddable:
vs @IdClass:
권장:
자연 키 (Natural Key):
비즈니스 의미 있는 PK:
- 주민등록번호 (사람)
- 이메일 (사용자)
- 사번 (직원)
- bl_no (배송)
특징:
- 의미 있음
- 외부에서 알 수 있음
- 변경 가능성
대리 키 (Surrogate Key):
의미 없는 자동 생성 PK:
- auto-increment Long
- UUID
- 시퀀스
특징:
- 의미 X
- 자동 생성
- 변경 불가능 (영원히)
| 항목 | 자연 키 | 대리 키 |
|---|---|---|
| 의미 | 있음 | 없음 |
| 생성 | 비즈니스 결정 | 자동 (DB) |
| 변경 | 가능 (사고 위험) | 절대 X |
| 외부 노출 | 위험 | 안전 |
| 크기 | 가변 | 고정 (Long 8bytes) |
| 인덱스 효율 | 보통 | 좋음 |
| FK 사용 | 큼 (참조 비용) | 작음 |
자연 키 의 문제:
1. 변경 가능성:
- 이메일 PK → 사용자 이메일 변경 시?
- FK 모두 변경 (재앙)
- 또는 변경 불가 (UX ↓)
2. 외부 노출:
- URL: /users/john@example.com
- 보안 / 프라이버시 ↓
3. 인덱스 비효율:
- String PK 길음
- 인덱스 크기 ↑
4. 외래 키 비용:
- FK 컬럼 크기 ↑
- 조인 성능 ↓
대리 키 의 장점:
1. 변경 불가:
- id 1 = 영원히 같은 행
- FK 안전
2. 외부 노출 안전:
- id 만 노출 (의미 없음)
- 보안 ↑
3. 효율:
- Long 8 bytes
- 인덱스 작음
- 빠른 JOIN
4. 일관:
- 모든 테이블 같은 패턴
→ 압도적 선호
실무 패턴:
대리 키 (PK):
- id BIGINT AUTO_INCREMENT
자연 키 (UNIQUE):
- bl_no UNIQUE
- email UNIQUE
- business_no UNIQUE
→ 둘 다 활용
-- ILIC 의 표준
CREATE TABLE shipments (
id BIGINT AUTO_INCREMENT PRIMARY KEY, -- 대리 키
bl_no VARCHAR(50) UNIQUE NOT NULL, -- 자연 키 (UNIQUE)
customer_id BIGINT,
status VARCHAR(20),
-- ...
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
-- bl_no 는 UNIQUE 이지만 PK X
-- 이유:
-- - bl_no 변경 가능 (오타 수정, 시스템 변경)
-- - PK 면 FK 모두 변경 (재앙)
-- - 대리 키 id 가 PK (안전)
// ILIC 엔티티
@Entity
@Table(name = "shipments",
uniqueConstraints = @UniqueConstraint(
name = "uk_shipments_bl_no",
columnNames = "bl_no"
)
)
public class Shipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 대리 키 (PK)
@Column(name = "bl_no", unique = true)
private String blNo; // 자연 키 (UNIQUE, PK X)
// ...
}
// 외부 API:
// - /shipments/123 (대리 키 id)
// - 검색은 bl_no 도 가능
// - 하지만 FK 는 id
자연 키 vs 대리 키 차이는?
답:
1. 자연 키:
대리 키:
실무:
혼용:
PK 타입 선택:
Long (BIGINT) ★★★:
- 가장 표준
- 8 bytes
- 21 quintillion 까지
- 빠름
Integer (INT):
- 21억 한계
- 작은 테이블만
UUID (16 bytes):
- 분산 환경
- 클라이언트 생성 가능
- 인덱스 비효율 (랜덤)
- 또는 ULID
String:
- 비즈니스 키 (자연 키)
- 보통 X
Long 의 우위:
- MySQL BIGINT 와 매핑
- auto-increment 표준
- 작고 빠름
- 외부 노출 안전
- JPA 와 잘 맞음
→ 실무 표준
UUID 사용 케이스:
- 분산 시스템 (여러 DB)
- 클라이언트가 PK 생성
- 외부 노출 시 추측 불가
단점:
- 16 bytes (크기 2배)
- 랜덤이라 인덱스 비효율
- DB 정렬 어색
→ 특수 시나리오
// equals / hashCode (id 기반)
@Entity
public class Shipment {
@Id
private Long id;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Shipment)) return false;
Shipment that = (Shipment) o;
// id null 체크 (비영속 상태)
return id != null && id.equals(that.id);
}
@Override
public int hashCode() {
// id 가 변할 수 있으니 클래스 기반 (Hibernate 권장)
return getClass().hashCode();
}
}
왜 이렇게?:
equals:
- id 비교 (PK 일치)
- id null 이면 false (비영속)
hashCode:
- id 사용 X (Hibernate 권장)
- id 가 null → not null 변할 수 있음
- HashSet 등에서 문제
- 클래스 hashCode 사용
// ❌ 위험 (Lombok)
@Entity
@EqualsAndHashCode // 모든 필드 기반!
public class Shipment {
// 모든 필드로 equals/hashCode
// 컬렉션, 연관관계 포함 → 문제
}
// ✅ id 만
@Entity
@EqualsAndHashCode(of = "id")
public class Shipment {
@Id
private Long id;
}
// 또는 직접 작성 (위 코드)
// ILIC 의 PK 표준
@Entity
@Table(name = "shipments")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Shipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@EqualsAndHashCode.Include
private Long id; // Long, IDENTITY
@Column(name = "bl_no", unique = true)
private String blNo;
@Column(name = "weight", precision = 10, scale = 2)
private BigDecimal weight;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
private Customer customer;
// ...
}
// ILIC 표준:
// - 모든 PK = Long
// - IDENTITY (MySQL auto-increment)
// - equals/hashCode = id 만
// - Lombok 활용 (안전 옵션)
class Customer {}
| Q | 핵심 답변 |
|---|---|
| @Id? | PK 필드 표시 |
| PK 역할? | 식별 / 캐시 |
| 필수? | 모든 엔티티 1개 |
| 없으면? | 시작 실패 |
| 단일 키? | Long auto-increment |
| 복합 키? | @IdClass / @EmbeddedId |
| 자연 vs 대리? | 대리 표준 |
| Long 권장? | 안전 / 효율 |
| equals? | id 기반 |
| hashCode? | 클래스 기반 |
답:
답:
답:
답:
답:
1. @Id = PK 매핑 필수
2. 단일 키 vs 복합 키
3. 자연 키 vs 대리 키
이번 Unit에서 @Id 를 봤다면, 다음은 @GeneratedValue (★ 깊이, PK 자동 생성 전략).
🏷️ Phase 4 — JPA 엔티티 매핑
✅ Unit 4.1 @Entity 와 엔티티 개념
✅ Unit 4.2 @Id 와 PK 매핑 ← 여기
⏭ Unit 4.3 @GeneratedValue 전략 ★깊이
⏭ Unit 4.4 @Column 과 컬럼 매핑
⏭ Unit 4.5 자동 매핑 규칙
🗂️ Part A — 데이터 모델링과 ORM
✅ Phase 1 (5)
✅ Phase 2 (2)
✅ Phase 3 (4)
🏷️ Phase 4 (2/5)
총: 13/24 Unit (54%)