7주차 Unit 4.3 — @GeneratedValue 전략

Psj·2026년 6월 1일

F-lab

목록 보기
226/239

Unit 4.3 — @GeneratedValue 전략

F-LAB JAVA · 7주차 · Phase 4 · JPA 엔티티 매핑
★ 깊이 파기 — PK 자동 생성 4가지 전략의 트레이드오프


📌 학습 목표

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

  • @GeneratedValue 의 정의는?
  • 4가지 전략 개요는?
  • IDENTITY 전략 동작은?
  • SEQUENCE 전략 동작은?
  • TABLE 전략 의 비효율은?
  • AUTO 전략 의 동작은?
  • IDENTITY 의 단점 (배치 INSERT 불가) 이유는?
  • SEQUENCE 가 IDENTITY 보다 빠른 이유 (allocationSize) 는?
  • 전략 선택 가이드 는?

🎯 핵심 한 문장

@GeneratedValue 는 PK 값을 자동으로 생성하는 4가지 전략 (IDENTITY · SEQUENCE · TABLE · AUTO) 을 제공하며, MySQL 의 IDENTITY 는 auto_increment 라 INSERT 후에야 ID 를 알 수 있어 영속성 컨텍스트의 쓰기 지연 (배치 INSERT) 이 불가능한 단점이 있고, Oracle/PostgreSQL 의 SEQUENCE 는 allocationSize 만큼 미리 받아 메모리에서 할당해 IDENTITY 보다 빠르며 배치 가능, TABLE 은 모든 DB 지원이지만 별도 락이 필요해 가장 느려서 거의 안 쓰고, 실무는 MySQL → IDENTITY · Oracle → SEQUENCE 가 표준이다.
@GeneratedValue@Id 와 함께 사용하는 어노테이션으로 PK 값을 자동 생성 하는 전략을 지정한다.
4가지 전략 — (1) IDENTITY (DB 의 auto_increment 사용, MySQL/PostgreSQL), (2) SEQUENCE (DB 시퀀스 객체 사용, Oracle/PostgreSQL), (3) TABLE (키 생성 전용 테이블, 모든 DB 지만 가장 느림), (4) AUTO (Hibernate 가 DB Dialect 보고 자동 선택, 기본값).
IDENTITY 의 결정적 단점 — INSERT 가 실행된 직후에만 DB 가 ID 를 알려주므로 JPA 의 쓰기 지연 (트랜잭션 commit 시 한 번에 INSERT) 이 불가능 하고, persist 호출 즉시 INSERT 가 발생해 배치 INSERT 가 불가능 하다 (대량 INSERT 시 성능 저하).
SEQUENCE 가 빠른 이유allocationSize (예: 50) 만큼 미리 시퀀스 값을 받아 메모리에 캐시하고, persist 호출 시 메모리에서 키 할당해 INSERT 를 지연 (배치) 시킬 수 있다 — 즉 시퀀스 호출 1번에 50개 엔티티 처리 가능.
실무는 — MySQL/MariaDB → IDENTITY (auto_increment), Oracle → SEQUENCE (allocationSize 50+), PostgreSQL → IDENTITY 또는 SEQUENCE, TABLE 은 거의 안 씀 — ILIC 의 102 테이블도 MySQL 의 IDENTITY 표준이다.

비유 — 식당의 대기번호 발급 방식

4가지 전략 = 대기번호 발급 4가지 방식:

[1] IDENTITY (현장 발급기):
  - 고객이 도착하면 → 발급기 누름 → 번호 받음
  - DB 의 auto_increment 와 동일
  - 한 명씩만 처리 (왕복)
  - 배치 X
  
  MySQL / PostgreSQL 표준

[2] SEQUENCE (예약 번호 묶음):
  - 매니저가 미리 50개 번호 받아둠
  - 고객 도착 시 그 중 하나 할당
  - 메모리에서 즉시 처리
  - 50명 한 번에 처리 (배치)
  - 50 번호 다 쓰면 다시 50개
  
  Oracle / PostgreSQL

[3] TABLE (수첩에 적어가며):
  - 별도 수첩에 마지막 번호 기록
  - 매번 수첩 잠그고 (락) → 번호 ↑ → 풀기
  - 매우 느림 (락 비용)
  
  거의 안 씀 (호환성 위해서만)

[4] AUTO (자동 판단):
  - 식당 타입 보고 (DB Dialect) 자동 선택
  - MySQL → 발급기 (IDENTITY)
  - Oracle → 예약 (SEQUENCE)
  
  기본값

IDENTITY 단점 (실무 중요):
  - 매 INSERT 마다 DB 왕복
  - 100건 INSERT = 100 왕복
  - JPA 쓰기 지연 (batch) X

SEQUENCE 우위:
  - 미리 50개 받아둠
  - 100건 INSERT = 2 시퀀스 호출 + 100 INSERT 한 번에
  - 빠름

ILIC:
  - MySQL = IDENTITY
  - 일반 CRUD 는 충분
  - 배치 INSERT 시 JdbcTemplate 보조

→ 4가지 전략, IDENTITY = 즉시 발급 (배치 X), SEQUENCE = 미리 받기 (배치 O).


🧭 9개 섹션 로드맵

1. @GeneratedValue 정의
2. 4가지 전략 개요
3. IDENTITY 전략
4. SEQUENCE 전략
5. TABLE 전략
6. AUTO 전략
7. IDENTITY 의 단점 (배치 X)
8. SEQUENCE 가 빠른 이유
9. 전략 선택 가이드

1️⃣ @GeneratedValue 정의

1.1 정의

@GeneratedValue:

  PK 값을 자동 생성:
    - @Id 와 함께
    - strategy 옵션 (생성 방식)
    - generator 옵션 (이름)

→ "PK 누가 만드나?"

1.2 기본 사용

import jakarta.persistence.*;

@Entity
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // ...
}

// strategy:
// - IDENTITY
// - SEQUENCE
// - TABLE
// - AUTO (기본값)

1.3 자동 생성 vs 수동

자동 vs 수동:

  자동 (@GeneratedValue):
    - DB / JPA 가 결정
    - 신경 X
    - 99% 케이스

  수동 (@GeneratedValue 없이):
    - 개발자가 명시
    - 자연 키 (직접 지정)
    - 드물게

  대부분 자동

1.4 GenerationType enum

package jakarta.persistence;

public enum GenerationType {
    TABLE,       // 키 생성 테이블 사용
    SEQUENCE,    // 시퀀스 사용
    IDENTITY,    // DB auto_increment
    AUTO,        // 자동 선택 (기본)
    UUID         // JPA 3.2+ 추가
}

1.5 ILIC 의 맥락

// ILIC 표준 (MySQL)
@Entity
@Table(name = "shipments")
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // ...
}

// 모든 102 테이블 동일
// - IDENTITY (MySQL auto_increment)
// - Long 타입

1.6 자기 점검 답변

@GeneratedValue 의 정의는?

:
1. @GeneratedValue:

  • PK 자동 생성
  1. 위치:

    • @Id 와 함께
  2. strategy:

    • 4가지 (+UUID)
  3. 자동:

    • 99% 케이스

2️⃣ 4가지 전략 개요

2.1 4가지 전략

전략동작적합 DB배치 INSERT
IDENTITYDB auto_incrementMySQL, PostgreSQL, SQL Server
SEQUENCEDB 시퀀스 객체Oracle, PostgreSQL, H2
TABLE별도 키 테이블모든 DB (느림)
AUTODialect 보고 자동모든 DB(선택된 전략 따라)

2.2 전략 비교 표

항목IDENTITYSEQUENCETABLEAUTO
사용처가장 흔함Oracle 위주호환성기본
키 발급INSERT 시 DB시퀀스 객체키 테이블
배치XO(선택 따라)
DB 내부가벼움무거움
표준비표준SQL 표준JPA 표준
성능보통빠름느림

2.3 적합성

DB 별 적합:

  MySQL / MariaDB:
    → IDENTITY (auto_increment)
    → SEQUENCE 미지원
    → TABLE 비효율

  Oracle:
    → SEQUENCE (시퀀스 객체)
    → IDENTITY (12c+) 가능
    → TABLE 비효율

  PostgreSQL:
    → IDENTITY 또는 SEQUENCE
    → 둘 다 좋음

  SQL Server:
    → IDENTITY
    → SEQUENCE (2012+)

  H2 (테스트):
    → IDENTITY / SEQUENCE 모두

2.4 표준화

JPA 표준:

  AUTO (기본):
    - DB 별 적합 자동
    - 호환성 ↑
    - 하지만 명시 권장 (실무)

  IDENTITY / SEQUENCE / TABLE:
    - 명시
    - 의도 명확

→ 명시 권장

2.5 ILIC 의 맥락

// ILIC = MySQL = IDENTITY
@Entity
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}

// 만약 Oracle 이면:
@Entity
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "shipment_seq")
    @SequenceGenerator(name = "shipment_seq",
                       sequenceName = "shipments_seq",
                       allocationSize = 50)
    private Long id;
}

// DB 별 다른 패턴

2.6 자기 점검 답변

4가지 전략 개요는?

:
1. IDENTITY:

  • auto_increment
  1. SEQUENCE:

    • 시퀀스
  2. TABLE:

    • 키 테이블
  3. AUTO:

    • 자동 선택

3️⃣ IDENTITY 전략

3.1 IDENTITY 의 동작

IDENTITY 의 동작:

  DB 의 auto_increment 활용:
    - INSERT 실행
    - DB 가 ID 값 결정
    - INSERT 후 generated key 반환
    - JPA 가 받음

3.2 DB 스키마

-- MySQL
CREATE TABLE shipments (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    bl_no VARCHAR(50),
    -- ...
);

-- PostgreSQL
CREATE TABLE shipments (
    id BIGSERIAL PRIMARY KEY,
    -- 또는
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    bl_no VARCHAR(50)
);

3.3 JPA 코드

@Entity
@Table(name = "shipments")
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String blNo;
    // ...
}

// persist 시:
Shipment s = new Shipment();
s.setBlNo("BL001");
em.persist(s);
// ↑ 이 시점에 INSERT 실행 (즉시)
// ↑ DB 가 ID 값 결정 후 반환
// ↑ s.id = 1 (자동 채움)
class Shipment { void setBlNo(String s) {} }
EntityManager em;
class EntityManager { void persist(Object o) {} }

3.4 INSERT 시점

INSERT 시점:

  IDENTITY 의 핵심:
    em.persist(entity)
        ↓ 즉시 INSERT 실행 (지연 X)
    DB 가 ID 값 결정
        ↓
    엔티티의 id 필드 채움
        ↓
    이후 영속성 컨텍스트 등록

→ 다른 전략과 결정적 차이
→ 다음 섹션 8에서 단점

3.5 SQL 로그

-- IDENTITY 의 SQL 로그
em.persist(s);
-- 즉시:
-- INSERT INTO shipments (bl_no, ...) VALUES (?, ...);
-- DB 가 id = 1 반환

em.persist(s2);
-- 즉시:
-- INSERT INTO shipments (bl_no, ...) VALUES (?, ...);
-- DB 가 id = 2 반환

-- 매 persist 마다 INSERT (왕복)

3.6 ILIC 의 맥락

// ILIC 의 IDENTITY 활용
@Entity
@Table(name = "shipments")
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // ...
}

@Service
@Transactional
public class ShipmentService {
    public Shipment create(String blNo) {
        Shipment s = Shipment.builder()
            .blNo(blNo)
            .build();
        
        repo.save(s);
        // ↑ 즉시 INSERT (IDENTITY)
        // s.getId() 사용 가능 (DB 가 채움)
        
        log.info("Created shipment with id={}", s.getId());
        return s;
    }
}
class Shipment {
    Long getId() { return null; }
    static Builder builder() { return null; }
    static class Builder {
        Builder blNo(String s) { return this; }
        Shipment build() { return null; }
    }
}
ShipmentRepository repo;
interface ShipmentRepository { Shipment save(Shipment s); }
org.slf4j.Logger log;

3.7 자기 점검 답변

IDENTITY 전략 동작은?

:
1. IDENTITY:

  • DB auto_increment
  1. DB:

    • MySQL/PostgreSQL/SQL Server
  2. 시점:

    • persist 시 즉시 INSERT
  3. ID:

    • DB 가 결정 후 반환

4️⃣ SEQUENCE 전략

4.1 시퀀스 객체

시퀀스 객체:

  DB 의 별도 객체:
    - 숫자 자동 증가
    - 테이블과 별개
    - 여러 테이블 공유 가능

  Oracle / PostgreSQL 표준

4.2 DB 스키마

-- Oracle
CREATE SEQUENCE shipments_seq
    START WITH 1
    INCREMENT BY 1
    NOCACHE;

-- PostgreSQL
CREATE SEQUENCE shipments_seq
    START WITH 1
    INCREMENT BY 1;

4.3 시퀀스 사용

-- 시퀀스에서 다음 값 (Oracle)
SELECT shipments_seq.NEXTVAL FROM dual;  -- 1
SELECT shipments_seq.NEXTVAL FROM dual;  -- 2

-- PostgreSQL
SELECT nextval('shipments_seq');  -- 1
SELECT nextval('shipments_seq');  -- 2

-- INSERT 시
INSERT INTO shipments (id, bl_no) 
VALUES (shipments_seq.NEXTVAL, 'BL001');  -- Oracle

4.4 JPA 코드

@Entity
@Table(name = "shipments")
@SequenceGenerator(
    name = "shipment_seq_gen",          // JPA 내부 이름
    sequenceName = "shipments_seq",     // DB 시퀀스명
    initialValue = 1,
    allocationSize = 50                  // 핵심!
)
public class Shipment {
    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "shipment_seq_gen"
    )
    private Long id;
    
    // ...
}

4.5 동작 흐름

SEQUENCE 동작:

  em.persist(entity)
      ↓
  JPA: "id 필요"
      ↓
  메모리 캐시 확인 (allocationSize)
      ↓
  ① 캐시 있음 → 즉시 할당 (SQL X)
  ② 캐시 없음 → 시퀀스 호출 (allocationSize 만큼)
      ↓
  엔티티 id 채움
      ↓
  트랜잭션 commit 시 INSERT (지연 가능)

→ INSERT 지연 (배치 가능)

4.6 SQL 로그

-- SEQUENCE 의 SQL 로그 (allocationSize = 50)
em.persist(s1);
-- 시퀀스 호출 (한 번에 50개 받음):
-- SELECT nextval('shipments_seq')   -- 50 반환
-- 메모리: 1, 2, 3, ..., 50

em.persist(s2);
-- 메모리에서 할당 (SQL X)
-- ...

em.persist(s50);
-- 메모리 51개째 → 또 시퀀스 호출
-- SELECT nextval('shipments_seq')   -- 100 반환

-- 트랜잭션 commit:
-- INSERT 1, INSERT 2, ..., INSERT 50  (한 번에 배치)

4.7 ILIC 의 맥락

// ILIC 가 Oracle 이라면 (가정)
@Entity
@Table(name = "shipments")
@SequenceGenerator(
    name = "shipment_seq",
    sequenceName = "shipments_seq",
    allocationSize = 50
)
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "shipment_seq")
    private Long id;
}

// 효과:
// - 대량 INSERT 시 시퀀스 호출 ↓
// - allocationSize = 50: 50건마다 1번 호출
// - 1000건 INSERT = 20 시퀀스 호출 (vs 1000)
// - 빠름

// 실제 ILIC = MySQL → IDENTITY
// 만약 Oracle 마이그레이션 시 SEQUENCE 로 변경

4.8 자기 점검 답변

SEQUENCE 전략 동작은?

:
1. 시퀀스:

  • DB 별도 객체
  1. DB:

    • Oracle/PostgreSQL
  2. allocationSize:

    • 미리 받음
  3. 장점:

    • 배치 가능

5️⃣ TABLE 전략

5.1 TABLE 의 동작

TABLE 의 동작:

  키 생성 전용 테이블:
    - 모든 키 정보 저장
    - SELECT → UPDATE 로 키 가져옴
    - 락 필요

  → 시퀀스 흉내 (모든 DB)

5.2 DB 스키마

-- 키 생성 테이블
CREATE TABLE id_generator (
    sequence_name VARCHAR(255) PRIMARY KEY,
    next_val BIGINT
);

INSERT INTO id_generator VALUES ('shipments', 1);
INSERT INTO id_generator VALUES ('customers', 1);
-- 각 엔티티별 행

5.3 JPA 코드

@Entity
@Table(name = "shipments")
@TableGenerator(
    name = "shipment_table_gen",
    table = "id_generator",
    pkColumnName = "sequence_name",
    valueColumnName = "next_val",
    pkColumnValue = "shipments",
    allocationSize = 50
)
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
                    generator = "shipment_table_gen")
    private Long id;
}

5.4 동작 흐름

TABLE 의 동작:

  em.persist(entity)
      ↓
  JPA: "id 필요"
      ↓
  id_generator 테이블에서:
    SELECT next_val FROM id_generator 
    WHERE sequence_name = 'shipments' FOR UPDATE;  -- 락
    
    UPDATE id_generator SET next_val = next_val + 50
    WHERE sequence_name = 'shipments';
      ↓
  메모리 캐시 (50개)
      ↓
  엔티티 id 채움

→ 락 (FOR UPDATE) 필요!

5.5 단점

TABLE 의 단점:

  1. 성능 ↓
     - SELECT + UPDATE 매번 (캐시 없을 때)
     - 락 비용
     - 시퀀스보다 훨씬 느림

  2. 병목
     - 모든 키 발급이 한 테이블
     - 동시 INSERT 시 락 경쟁
     - 처리량 ↓

  3. 운영 부담
     - id_generator 테이블 백업 / 동기화

→ 거의 안 씀

5.6 사용 시기

사용 시기 (드묾):

  - 모든 DB 지원 필요 (특수)
  - 시퀀스 없는 DB
  - 다중 DB 환경

  실무는 거의 안 씀

5.7 ILIC 의 맥락

ILIC 의 TABLE 전략 (X)

ILIC = MySQL = IDENTITY:
  - TABLE 사용 X
  - 사용 이유 X

TABLE 의 시나리오:
  - 만약 다중 DB (MySQL + Oracle + ...) 환경
  - 동일 코드로 모두 지원 필요
  - 그래도 보통 AUTO 사용

→ ILIC 같은 단일 DB 환경에선 TABLE 불필요

5.8 자기 점검 답변

TABLE 전략의 비효율은?

:
1. TABLE:

  • 키 생성 테이블
  1. 동작:

    • SELECT + UPDATE + 락
  2. 단점:

    • 느림 / 병목
  3. 사용:

    • 거의 X

6️⃣ AUTO 전략

6.1 AUTO 의 동작

AUTO 의 동작:

  Hibernate 가 DB Dialect 보고 자동 선택:
    - MySQL Dialect → IDENTITY
    - Oracle Dialect → SEQUENCE
    - PostgreSQL → SEQUENCE (또는 IDENTITY)
    - 기타 → TABLE

  기본값

6.2 사용

@Entity
public class Shipment {
    @Id
    @GeneratedValue   // strategy 생략 = AUTO
    private Long id;
    
    // 또는
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
}

6.3 장점

장점:

  - DB 무관 (Dialect 자동)
  - 의존성 적음
  - 마이그레이션 쉬움
  - 빠른 시작

6.4 단점

단점:

  - 어떤 전략 쓰는지 불명확
  - DB 따라 다른 동작
  - 의도 명확 X

→ 실무는 명시 권장

6.5 명시 vs AUTO

명시 vs AUTO:

  AUTO (기본):
    - 빠른 개발 / 학습
    - 단일 DB 환경
    - 또는 신중 검토 후 OK

  명시 (실무):
    - IDENTITY / SEQUENCE 직접
    - 의도 명확
    - 코드 리뷰 친화

→ 실무는 명시

6.6 Hibernate 5 vs 6 변화

Hibernate 5 vs 6:

  Hibernate 5:
    - AUTO + MySQL → TABLE (호환성)
    - 비효율
    - 깜짝 놀람 / 함정

  Hibernate 6:
    - AUTO + MySQL → IDENTITY (개선)
    - 더 합리적

→ 버전 따라 동작 다름 → 명시 권장

6.7 ILIC 의 맥락

// ILIC 의 정책: 명시
@Entity
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // 명시
    private Long id;
}

// 명시 이유:
// 1. 의도 명확 (이 코드 = MySQL IDENTITY)
// 2. Hibernate 버전 의존 X
// 3. 코드 리뷰에서 즉시 이해
// 4. 마이그레이션 시 결정 명확

// AUTO 는:
// - 학습 단계
// - 또는 신중 결정

6.8 자기 점검 답변

AUTO 전략의 동작은?

:
1. AUTO:

  • Dialect 자동 선택
  1. 기본값:

    • strategy 생략
  2. 장단점:

    • 편함 vs 불명확
  3. 실무:

    • 명시 권장

7️⃣ IDENTITY 의 단점 (배치 X)

7.1 결정적 단점

결정적 단점:

  IDENTITY = persist 즉시 INSERT 실행:
    - DB 가 ID 결정해야 하므로
    - 영속성 컨텍스트의 쓰기 지연 X
    - 배치 INSERT 불가능

→ 대량 INSERT 시 느림

7.2 쓰기 지연의 의미

쓰기 지연 (Write-Behind):

  JPA 의 기본 동작:
    em.persist(entity1);   // 메모리만 (SQL X)
    em.persist(entity2);   // 메모리만
    em.persist(entity3);   // 메모리만
    
    // 트랜잭션 commit 시
    flush:
      INSERT 1, INSERT 2, INSERT 3 (배치)

  IDENTITY 는:
    em.persist(entity1);
    // → 즉시 INSERT 1 (DB 가 ID 결정해야 함)
    em.persist(entity2);
    // → 즉시 INSERT 2
    ...

  → 매번 즉시 → 배치 X

7.3 배치 INSERT (SEQUENCE/TABLE)

-- SEQUENCE/TABLE 의 배치
-- 트랜잭션 commit 시
INSERT INTO shipments (id, bl_no) VALUES 
    (1, 'BL001'),
    (2, 'BL002'),
    (3, 'BL003'),
    ...;
-- 한 번의 SQL (배치)
-- 또는 PreparedStatement.addBatch()

7.4 IDENTITY 의 매 INSERT

-- IDENTITY 의 매 INSERT
INSERT INTO shipments (bl_no) VALUES ('BL001');
-- → DB 가 id = 1 결정 → 반환
INSERT INTO shipments (bl_no) VALUES ('BL002');
-- → DB 가 id = 2 결정 → 반환
INSERT INTO shipments (bl_no) VALUES ('BL003');
-- → DB 가 id = 3 결정 → 반환

-- 1000건 = 1000 왕복!

7.5 성능 차이

성능 차이 (1000건 INSERT):

  IDENTITY:
    - 1000 INSERT (왕복)
    - 매번 ID 받아옴
    - 1000번 × 네트워크 왕복
    - 느림 (초 단위)

  SEQUENCE (allocationSize=50):
    - 20 시퀀스 호출 + 1000 INSERT 배치
    - 또는 1 INSERT (multi-value)
    - 빠름 (밀리초)

  → 10배 이상 차이

7.6 ILIC 의 맥락

// ILIC 의 대량 INSERT 처리

// IDENTITY (느림)
@Service
public class ShipmentService {
    public void importBatch(List<ShipmentDto> dtos) {
        for (ShipmentDto dto : dtos) {
            Shipment s = Shipment.builder()
                .blNo(dto.blNo())
                .build();
            repo.save(s);
            // → 매번 즉시 INSERT
            // → 1000건 = 1000 INSERT
            // → 느림
        }
    }
}

// 해결 1: JdbcTemplate batchUpdate (6주차)
@Service
public class ShipmentBatchService {
    @Autowired JdbcTemplate jdbcTemplate;
    
    public void importBatchFast(List<ShipmentDto> dtos) {
        List<Object[]> batch = dtos.stream()
            .map(d -> new Object[] { d.blNo() })
            .collect(Collectors.toList());
        
        jdbcTemplate.batchUpdate(
            "INSERT INTO shipments (bl_no) VALUES (?)",
            batch
        );
        // → 한 번에 INSERT (배치)
        // → 빠름
    }
}

// 해결 2: JPA Hint (제한적)
// hibernate.jdbc.batch_size 설정
// 하지만 IDENTITY 는 효과 X

// → ILIC 의 대량 처리는 JdbcTemplate 보조
class Shipment {
    static Builder builder() { return null; }
    static class Builder {
        Builder blNo(String s) { return this; }
        Shipment build() { return null; }
    }
}
record ShipmentDto(String blNo) {}
ShipmentRepository repo;
interface ShipmentRepository { Shipment save(Shipment s); }
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    void batchUpdate(String s, java.util.List<Object[]> b) {}
}

7.7 자기 점검 답변

IDENTITY 의 단점 (배치 INSERT 불가) 이유는?

:
1. 배치 X:

  • 즉시 INSERT
  1. 이유:

    • DB 가 ID 결정 (INSERT 후)
  2. 쓰기 지연:

    • 불가능
  3. 대안:

    • JdbcTemplate batchUpdate

8️⃣ SEQUENCE 가 빠른 이유

8.1 allocationSize 의 마법

allocationSize 의 마법:

  설정:
    @SequenceGenerator(allocationSize = 50)

  동작:
    - 시퀀스 호출 1번 → 50 값 받음
    - 메모리에 50개 캐시 (1~50)
    - 메모리에서 즉시 할당
    - INSERT 지연 가능 (배치)

→ 시퀀스 호출 ↓
→ INSERT 배치 가능

8.2 동작 흐름 (allocationSize=50)

동작 흐름:

  em.persist(s1)  // id 필요
    → 시퀀스 호출: nextval → 50 반환
    → 메모리: 1, 2, ..., 50 (50개 캐시)
    → s1.id = 1 할당
    → INSERT 지연 (메모리만)

  em.persist(s2)  // id 필요
    → 메모리에서 즉시 (SQL X)
    → s2.id = 2 할당

  ... (49번 더)

  em.persist(s51) // id 필요
    → 메모리 비었음
    → 시퀀스 호출: nextval → 100 반환
    → 메모리: 51, 52, ..., 100
    → s51.id = 51 할당

  ...

  트랜잭션 commit:
    INSERT 1, 2, ..., 50 (배치)
    INSERT 51, 52, ..., 100 (배치)

8.3 시퀀스 호출 ↓

시퀀스 호출 ↓:

  allocationSize = 1:
    - 1000건 → 1000 시퀀스 호출
    - 비효율

  allocationSize = 50:
    - 1000건 → 20 시퀀스 호출
    - 효율 ↑

  allocationSize = 100:
    - 1000건 → 10 시퀀스 호출
    - 더 효율

→ allocationSize 가 클수록 좋음

8.4 INSERT 배치 가능

INSERT 배치 가능:

  SEQUENCE 는:
    - 메모리에서 ID 미리 알 수 있음
    - INSERT 지연 가능
    - 트랜잭션 commit 시 한 번에

  hibernate.jdbc.batch_size = 50:
    - JDBC 배치 활성화
    - INSERT 50개씩 묶어 실행
    - 네트워크 왕복 ↓

→ 매우 빠름

8.5 allocationSize 의 트레이드오프

allocationSize 의 트레이드오프:

  크면 (예: 1000):
    - 시퀀스 호출 ↓
    - 빠름
    - 단점: 애플리케이션 재시작 시 ID 손실
      (메모리에 999개 있었는데 안 쓰고 종료)
      → 다음 시작 시 + 1000

  작으면 (예: 1):
    - 시퀀스 호출 ↑ (느림)
    - 손실 없음

  실무:
    - 50 ~ 100 (균형)
    - 50 이 일반

8.6 ID 손실 (정확히 연속 X)

ID 손실:

  allocationSize = 50:
    - 메모리 1~50 받음
    - 1, 2, 3 사용 후 종료
    - 다음 시작: 시퀀스 호출 → 100 받음
    - 4~50 영영 사용 X (손실)

  → 연속된 ID 보장 X
  → 비즈니스 의미 부여 X (대리 키)

  대리 키니까 OK

8.7 ILIC 의 맥락

// ILIC 가 PostgreSQL 이라면 (가정)
@Entity
@Table(name = "shipments")
@SequenceGenerator(
    name = "shipment_seq",
    sequenceName = "shipments_seq",
    allocationSize = 50    // 50씩 미리
)
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "shipment_seq")
    private Long id;
}

// application.yml
spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 50           # JDBC 배치
        order_inserts: true        # INSERT 정렬
        order_updates: true

// 효과 (대량 INSERT):
// - 시퀀스 호출 ↓ (1000건당 20번)
// - JDBC 배치 (50건 묶음)
// - 매우 빠름

// 실제 ILIC = MySQL → IDENTITY
// → 배치 INSERT 시 JdbcTemplate 또는 native batch

8.8 자기 점검 답변

SEQUENCE 가 IDENTITY 보다 빠른 이유는?

:
1. allocationSize:

  • 미리 받음
  1. 메모리:

    • 즉시 할당
  2. 시퀀스 호출:

  3. INSERT 배치:

    • 가능

9️⃣ 전략 선택 가이드

9.1 의사 결정 흐름

의사 결정 흐름:

  Q: 어떤 DB?
  
  A. MySQL / MariaDB:
     → IDENTITY (auto_increment)
     → 시퀀스 미지원

  B. Oracle:
     → SEQUENCE (allocationSize = 50)
     → 12c+ 면 IDENTITY 도 가능
     
  C. PostgreSQL:
     → IDENTITY (BIGSERIAL)
     → 또는 SEQUENCE

  D. SQL Server:
     → IDENTITY
     → 2012+ SEQUENCE

  E. 모든 DB:
     → AUTO (편의)
     → 또는 명시

9.2 ILIC 의 결정

ILIC 의 결정:

  DB: MySQL 8.x
  전략: IDENTITY

  이유:
    - MySQL 시퀀스 X
    - auto_increment 표준
    - 일관성

  단점 (배치 X):
    - 대량 INSERT 시 JdbcTemplate 보조
    - JPA + JdbcTemplate 혼용

  → ILIC 표준

9.3 비교 매트릭스

시나리오추천 전략
MySQL 신규 프로젝트IDENTITY
Oracle 기존 프로젝트SEQUENCE
PostgreSQLIDENTITY 또는 SEQUENCE
다중 DB 지원AUTO
대량 INSERT 빈번SEQUENCE + allocationSize 큰 값
학습 / 단순AUTO
호환성 (모든 DB)TABLE (느림, 거의 X)

9.4 면접 단골 질문

면접 단골:

  Q: MySQL 에서 IDENTITY 의 단점?
  A: 배치 INSERT 불가 (persist 즉시 INSERT)

  Q: SEQUENCE 의 allocationSize?
  A: 미리 받는 키 개수, 50 이 일반

  Q: SEQUENCE 가 IDENTITY 보다 빠른 이유?
  A: 메모리 할당 + 배치 가능

  Q: AUTO 의 권장 여부?
  A: 명시 권장 (실무)

  Q: TABLE 의 사용?
  A: 거의 X (느림)

9.5 대량 INSERT 최적화

대량 INSERT 최적화:

  MySQL (IDENTITY) 시:
    1. JdbcTemplate.batchUpdate
    2. Multi-value INSERT
       (INSERT VALUES (...), (...), (...))
    3. LOAD DATA INFILE (가장 빠름)
    4. JPA 는 부적합

  Oracle (SEQUENCE) 시:
    1. JPA + batch_size = 50
    2. allocationSize 크게 (100+)
    3. JdbcTemplate 도 가능

9.6 ILIC 의 맥락

// ILIC 의 종합 전략

// 1. 일반 CRUD: JPA IDENTITY
@Entity
public class Shipment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}

// 2. 대량 INSERT: JdbcTemplate
@Service
public class ShipmentImportService {
    @Autowired JdbcTemplate jdbcTemplate;
    
    public void importFromCsv(List<ShipmentDto> shipments) {
        List<Object[]> batch = shipments.stream()
            .map(s -> new Object[] { s.blNo(), s.weight() })
            .toList();
        
        jdbcTemplate.batchUpdate(
            "INSERT INTO shipments (bl_no, weight) VALUES (?, ?)",
            batch
        );
        // → 한 번에 INSERT (네트워크 1회)
        // → 빠름
    }
}

// ILIC 의 종합:
// - JPA = 일반 CRUD (Spring Data JPA + IDENTITY)
// - JdbcTemplate = 대량 처리 + 복잡 통계
// - 6주차 + 7주차 도구 혼용
class Shipment {}
record ShipmentDto(String blNo, java.math.BigDecimal weight) {}
JdbcTemplate jdbcTemplate;
class JdbcTemplate {
    void batchUpdate(String s, java.util.List<Object[]> b) {}
}

9.7 면접 단골 질문 매핑

Q핵심 답변
@GeneratedValue?PK 자동 생성
4가지 전략?IDENTITY/SEQUENCE/TABLE/AUTO
IDENTITY 동작?auto_increment
SEQUENCE 동작?시퀀스 객체
TABLE?키 테이블 (느림)
AUTO?Dialect 자동
IDENTITY 단점?배치 X
SEQUENCE 빠른 이유?allocationSize
선택?DB 따라
대량 INSERT?JdbcTemplate 보조

9.8 자기 점검 체크리스트

정의

  • @GeneratedValue

4가지

  • 전략

IDENTITY

  • auto_increment

SEQUENCE

  • 시퀀스

TABLE

  • 키 테이블

AUTO

  • 자동

IDENTITY 단점

  • 배치 X

SEQUENCE 빠름

  • allocationSize

선택

  • DB 따라

9.9 추가 심화 질문

Q1: hibernate.id.new_generator_mappings?

답:

  • Hibernate 5+: true (기본)
  • SEQUENCE 동작 변경 (allocationSize 의미)
  • 옛 버전과 호환성

Q2: BIGSERIAL (PostgreSQL) 과 IDENTITY?

답:

  • BIGSERIAL = BIGINT + 시퀀스
  • 내부적으로 시퀀스 사용
  • IDENTITY 와 결과 같음
  • 명시적으로 다른 키워드

Q3: 분산 환경의 ID?

답:

  • IDENTITY/SEQUENCE 부적합 (단일 DB)
  • UUID (랜덤)
  • Snowflake ID (Twitter, 시간 + 노드)
  • ULID

Q4: optimizer 옵션?

답:

  • pooled / pooled-lo (SEQUENCE)
  • 시퀀스 호출 패턴
  • 기본 pooled

Q5: Custom ID Generator?

답:

  • @IdGeneratorType (Hibernate 6+)
  • 또는 @GenericGenerator
  • 비즈니스 키 생성 (예: ORDER-2024-001)

🎯 핵심 요약 — 3줄 정리

1. 4가지 전략

  • IDENTITY: DB auto_increment (MySQL/PostgreSQL), persist 즉시 INSERT, 배치 X
  • SEQUENCE: 시퀀스 객체 (Oracle/PostgreSQL), allocationSize 미리 받음, 배치 O
  • TABLE: 키 테이블 + 락 (모든 DB, 느림, 거의 X)
  • AUTO: Dialect 자동 (기본값, 실무는 명시 권장)

2. IDENTITY 의 결정적 단점

  • DB 가 INSERT 후에야 ID 결정 → JPA 의 쓰기 지연 (배치 INSERT) 불가능
  • persist 호출 즉시 INSERT 실행 (1000건 = 1000 왕복)
  • 대량 INSERT 시 JdbcTemplate 보조 (6주차)

3. SEQUENCE 의 우위

  • allocationSize (예: 50) 만큼 시퀀스 미리 받아 메모리 캐시
  • 메모리에서 ID 즉시 할당 → INSERT 지연 가능 → 배치 INSERT
  • 1000건 = 20 시퀀스 호출 + 1 배치 INSERT (10배+ 빠름)

📚 다음으로...

Unit 4.4 — @Column 과 컬럼 매핑

이번 Unit에서 PK 자동 생성을 봤다면, 다음은 @Column (일반 컬럼 매핑).

  • @Column 옵션 (name/length/nullable/unique)
  • 자동 매핑과 명시
  • DDL 생성 시 활용
  • 사용자 정의 타입 (@Convert)

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 자동 매핑 규칙

7주차 누적 진행

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

총: 14/24 Unit (58%)

★ 깊이 파기 — @GeneratedValue 전략 완료

profile
Software Developer

0개의 댓글