F-LAB JAVA · 7주차 · Phase 4 · JPA 엔티티 매핑
★ 깊이 파기 — PK 자동 생성 4가지 전략의 트레이드오프
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
@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).
1. @GeneratedValue 정의
2. 4가지 전략 개요
3. IDENTITY 전략
4. SEQUENCE 전략
5. TABLE 전략
6. AUTO 전략
7. IDENTITY 의 단점 (배치 X)
8. SEQUENCE 가 빠른 이유
9. 전략 선택 가이드
@GeneratedValue:
PK 값을 자동 생성:
- @Id 와 함께
- strategy 옵션 (생성 방식)
- generator 옵션 (이름)
→ "PK 누가 만드나?"
import jakarta.persistence.*;
@Entity
public class Shipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ...
}
// strategy:
// - IDENTITY
// - SEQUENCE
// - TABLE
// - AUTO (기본값)
자동 vs 수동:
자동 (@GeneratedValue):
- DB / JPA 가 결정
- 신경 X
- 99% 케이스
수동 (@GeneratedValue 없이):
- 개발자가 명시
- 자연 키 (직접 지정)
- 드물게
대부분 자동
package jakarta.persistence;
public enum GenerationType {
TABLE, // 키 생성 테이블 사용
SEQUENCE, // 시퀀스 사용
IDENTITY, // DB auto_increment
AUTO, // 자동 선택 (기본)
UUID // JPA 3.2+ 추가
}
// ILIC 표준 (MySQL)
@Entity
@Table(name = "shipments")
public class Shipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// ...
}
// 모든 102 테이블 동일
// - IDENTITY (MySQL auto_increment)
// - Long 타입
@GeneratedValue 의 정의는?
답:
1. @GeneratedValue:
위치:
strategy:
자동:
| 전략 | 동작 | 적합 DB | 배치 INSERT |
|---|---|---|---|
| IDENTITY | DB auto_increment | MySQL, PostgreSQL, SQL Server | ❌ |
| SEQUENCE | DB 시퀀스 객체 | Oracle, PostgreSQL, H2 | ✅ |
| TABLE | 별도 키 테이블 | 모든 DB (느림) | △ |
| AUTO | Dialect 보고 자동 | 모든 DB | (선택된 전략 따라) |
| 항목 | IDENTITY | SEQUENCE | TABLE | AUTO |
|---|---|---|---|---|
| 사용처 | 가장 흔함 | Oracle 위주 | 호환성 | 기본 |
| 키 발급 | INSERT 시 DB | 시퀀스 객체 | 키 테이블 | |
| 배치 | X | O | △ | (선택 따라) |
| 락 | DB 내부 | 가벼움 | 무거움 | |
| 표준 | 비표준 | SQL 표준 | JPA 표준 | |
| 성능 | 보통 | 빠름 | 느림 |
DB 별 적합:
MySQL / MariaDB:
→ IDENTITY (auto_increment)
→ SEQUENCE 미지원
→ TABLE 비효율
Oracle:
→ SEQUENCE (시퀀스 객체)
→ IDENTITY (12c+) 가능
→ TABLE 비효율
PostgreSQL:
→ IDENTITY 또는 SEQUENCE
→ 둘 다 좋음
SQL Server:
→ IDENTITY
→ SEQUENCE (2012+)
H2 (테스트):
→ IDENTITY / SEQUENCE 모두
JPA 표준:
AUTO (기본):
- DB 별 적합 자동
- 호환성 ↑
- 하지만 명시 권장 (실무)
IDENTITY / SEQUENCE / TABLE:
- 명시
- 의도 명확
→ 명시 권장
// 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 별 다른 패턴
4가지 전략 개요는?
답:
1. IDENTITY:
SEQUENCE:
TABLE:
AUTO:
IDENTITY 의 동작:
DB 의 auto_increment 활용:
- INSERT 실행
- DB 가 ID 값 결정
- INSERT 후 generated key 반환
- JPA 가 받음
-- 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)
);
@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) {} }
INSERT 시점:
IDENTITY 의 핵심:
em.persist(entity)
↓ 즉시 INSERT 실행 (지연 X)
DB 가 ID 값 결정
↓
엔티티의 id 필드 채움
↓
이후 영속성 컨텍스트 등록
→ 다른 전략과 결정적 차이
→ 다음 섹션 8에서 단점
-- 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 (왕복)
// 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;
IDENTITY 전략 동작은?
답:
1. IDENTITY:
DB:
시점:
ID:
시퀀스 객체:
DB 의 별도 객체:
- 숫자 자동 증가
- 테이블과 별개
- 여러 테이블 공유 가능
Oracle / PostgreSQL 표준
-- Oracle
CREATE SEQUENCE shipments_seq
START WITH 1
INCREMENT BY 1
NOCACHE;
-- PostgreSQL
CREATE SEQUENCE shipments_seq
START WITH 1
INCREMENT BY 1;
-- 시퀀스에서 다음 값 (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
@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;
// ...
}
SEQUENCE 동작:
em.persist(entity)
↓
JPA: "id 필요"
↓
메모리 캐시 확인 (allocationSize)
↓
① 캐시 있음 → 즉시 할당 (SQL X)
② 캐시 없음 → 시퀀스 호출 (allocationSize 만큼)
↓
엔티티 id 채움
↓
트랜잭션 commit 시 INSERT (지연 가능)
→ INSERT 지연 (배치 가능)
-- 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 (한 번에 배치)
// 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 로 변경
SEQUENCE 전략 동작은?
답:
1. 시퀀스:
DB:
allocationSize:
장점:
TABLE 의 동작:
키 생성 전용 테이블:
- 모든 키 정보 저장
- SELECT → UPDATE 로 키 가져옴
- 락 필요
→ 시퀀스 흉내 (모든 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);
-- 각 엔티티별 행
@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;
}
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) 필요!
TABLE 의 단점:
1. 성능 ↓
- SELECT + UPDATE 매번 (캐시 없을 때)
- 락 비용
- 시퀀스보다 훨씬 느림
2. 병목
- 모든 키 발급이 한 테이블
- 동시 INSERT 시 락 경쟁
- 처리량 ↓
3. 운영 부담
- id_generator 테이블 백업 / 동기화
→ 거의 안 씀
사용 시기 (드묾):
- 모든 DB 지원 필요 (특수)
- 시퀀스 없는 DB
- 다중 DB 환경
실무는 거의 안 씀
ILIC 의 TABLE 전략 (X)
ILIC = MySQL = IDENTITY:
- TABLE 사용 X
- 사용 이유 X
TABLE 의 시나리오:
- 만약 다중 DB (MySQL + Oracle + ...) 환경
- 동일 코드로 모두 지원 필요
- 그래도 보통 AUTO 사용
→ ILIC 같은 단일 DB 환경에선 TABLE 불필요
TABLE 전략의 비효율은?
답:
1. TABLE:
동작:
단점:
사용:
AUTO 의 동작:
Hibernate 가 DB Dialect 보고 자동 선택:
- MySQL Dialect → IDENTITY
- Oracle Dialect → SEQUENCE
- PostgreSQL → SEQUENCE (또는 IDENTITY)
- 기타 → TABLE
기본값
@Entity
public class Shipment {
@Id
@GeneratedValue // strategy 생략 = AUTO
private Long id;
// 또는
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
장점:
- DB 무관 (Dialect 자동)
- 의존성 적음
- 마이그레이션 쉬움
- 빠른 시작
단점:
- 어떤 전략 쓰는지 불명확
- DB 따라 다른 동작
- 의도 명확 X
→ 실무는 명시 권장
명시 vs AUTO:
AUTO (기본):
- 빠른 개발 / 학습
- 단일 DB 환경
- 또는 신중 검토 후 OK
명시 (실무):
- IDENTITY / SEQUENCE 직접
- 의도 명확
- 코드 리뷰 친화
→ 실무는 명시
Hibernate 5 vs 6:
Hibernate 5:
- AUTO + MySQL → TABLE (호환성)
- 비효율
- 깜짝 놀람 / 함정
Hibernate 6:
- AUTO + MySQL → IDENTITY (개선)
- 더 합리적
→ 버전 따라 동작 다름 → 명시 권장
// ILIC 의 정책: 명시
@Entity
public class Shipment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 명시
private Long id;
}
// 명시 이유:
// 1. 의도 명확 (이 코드 = MySQL IDENTITY)
// 2. Hibernate 버전 의존 X
// 3. 코드 리뷰에서 즉시 이해
// 4. 마이그레이션 시 결정 명확
// AUTO 는:
// - 학습 단계
// - 또는 신중 결정
AUTO 전략의 동작은?
답:
1. AUTO:
기본값:
장단점:
실무:
결정적 단점:
IDENTITY = persist 즉시 INSERT 실행:
- DB 가 ID 결정해야 하므로
- 영속성 컨텍스트의 쓰기 지연 X
- 배치 INSERT 불가능
→ 대량 INSERT 시 느림
쓰기 지연 (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
-- SEQUENCE/TABLE 의 배치
-- 트랜잭션 commit 시
INSERT INTO shipments (id, bl_no) VALUES
(1, 'BL001'),
(2, 'BL002'),
(3, 'BL003'),
...;
-- 한 번의 SQL (배치)
-- 또는 PreparedStatement.addBatch()
-- 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 왕복!
성능 차이 (1000건 INSERT):
IDENTITY:
- 1000 INSERT (왕복)
- 매번 ID 받아옴
- 1000번 × 네트워크 왕복
- 느림 (초 단위)
SEQUENCE (allocationSize=50):
- 20 시퀀스 호출 + 1000 INSERT 배치
- 또는 1 INSERT (multi-value)
- 빠름 (밀리초)
→ 10배 이상 차이
// 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) {}
}
IDENTITY 의 단점 (배치 INSERT 불가) 이유는?
답:
1. 배치 X:
이유:
쓰기 지연:
대안:
allocationSize 의 마법:
설정:
@SequenceGenerator(allocationSize = 50)
동작:
- 시퀀스 호출 1번 → 50 값 받음
- 메모리에 50개 캐시 (1~50)
- 메모리에서 즉시 할당
- INSERT 지연 가능 (배치)
→ 시퀀스 호출 ↓
→ INSERT 배치 가능
동작 흐름:
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 (배치)
시퀀스 호출 ↓:
allocationSize = 1:
- 1000건 → 1000 시퀀스 호출
- 비효율
allocationSize = 50:
- 1000건 → 20 시퀀스 호출
- 효율 ↑
allocationSize = 100:
- 1000건 → 10 시퀀스 호출
- 더 효율
→ allocationSize 가 클수록 좋음
INSERT 배치 가능:
SEQUENCE 는:
- 메모리에서 ID 미리 알 수 있음
- INSERT 지연 가능
- 트랜잭션 commit 시 한 번에
hibernate.jdbc.batch_size = 50:
- JDBC 배치 활성화
- INSERT 50개씩 묶어 실행
- 네트워크 왕복 ↓
→ 매우 빠름
allocationSize 의 트레이드오프:
크면 (예: 1000):
- 시퀀스 호출 ↓
- 빠름
- 단점: 애플리케이션 재시작 시 ID 손실
(메모리에 999개 있었는데 안 쓰고 종료)
→ 다음 시작 시 + 1000
작으면 (예: 1):
- 시퀀스 호출 ↑ (느림)
- 손실 없음
실무:
- 50 ~ 100 (균형)
- 50 이 일반
ID 손실:
allocationSize = 50:
- 메모리 1~50 받음
- 1, 2, 3 사용 후 종료
- 다음 시작: 시퀀스 호출 → 100 받음
- 4~50 영영 사용 X (손실)
→ 연속된 ID 보장 X
→ 비즈니스 의미 부여 X (대리 키)
대리 키니까 OK
// 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
SEQUENCE 가 IDENTITY 보다 빠른 이유는?
답:
1. allocationSize:
메모리:
시퀀스 호출:
INSERT 배치:
의사 결정 흐름:
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 (편의)
→ 또는 명시
ILIC 의 결정:
DB: MySQL 8.x
전략: IDENTITY
이유:
- MySQL 시퀀스 X
- auto_increment 표준
- 일관성
단점 (배치 X):
- 대량 INSERT 시 JdbcTemplate 보조
- JPA + JdbcTemplate 혼용
→ ILIC 표준
| 시나리오 | 추천 전략 |
|---|---|
| MySQL 신규 프로젝트 | IDENTITY |
| Oracle 기존 프로젝트 | SEQUENCE |
| PostgreSQL | IDENTITY 또는 SEQUENCE |
| 다중 DB 지원 | AUTO |
| 대량 INSERT 빈번 | SEQUENCE + allocationSize 큰 값 |
| 학습 / 단순 | AUTO |
| 호환성 (모든 DB) | TABLE (느림, 거의 X) |
면접 단골:
Q: MySQL 에서 IDENTITY 의 단점?
A: 배치 INSERT 불가 (persist 즉시 INSERT)
Q: SEQUENCE 의 allocationSize?
A: 미리 받는 키 개수, 50 이 일반
Q: SEQUENCE 가 IDENTITY 보다 빠른 이유?
A: 메모리 할당 + 배치 가능
Q: AUTO 의 권장 여부?
A: 명시 권장 (실무)
Q: TABLE 의 사용?
A: 거의 X (느림)
대량 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 도 가능
// 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) {}
}
| Q | 핵심 답변 |
|---|---|
| @GeneratedValue? | PK 자동 생성 |
| 4가지 전략? | IDENTITY/SEQUENCE/TABLE/AUTO |
| IDENTITY 동작? | auto_increment |
| SEQUENCE 동작? | 시퀀스 객체 |
| TABLE? | 키 테이블 (느림) |
| AUTO? | Dialect 자동 |
| IDENTITY 단점? | 배치 X |
| SEQUENCE 빠른 이유? | allocationSize |
| 선택? | DB 따라 |
| 대량 INSERT? | JdbcTemplate 보조 |
답:
답:
답:
답:
답:
1. 4가지 전략
2. IDENTITY 의 결정적 단점
3. SEQUENCE 의 우위
이번 Unit에서 PK 자동 생성을 봤다면, 다음은 @Column (일반 컬럼 매핑).
🏷️ 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 (3/5)
총: 14/24 Unit (58%)
★ 깊이 파기 — @GeneratedValue 전략 완료