Posts JPA/Hibernate ID 생성 전략
Post
Cancel

JPA/Hibernate ID 생성 전략

@GeneratedValue


기본 키(primary key) 값의 생성 전략을 지정하는 데 사용
즉, 데이터베이스에 새 레코드를 삽입할 때 기본 키 값을 자동으로 생성하는 방법을 지정

  • @GeneratedValue@Id와 함께 엔티티 또는 매핑된 슈퍼클래스의 기본 키 속성이나 필드에 적용할 수 있다.
  • @GeneratedValue는 단순 기본 키(단일 컬럼으로 구성된 PK)에 대해서만 지원된다.
    • 파생 기본 키(@MapsId로 다른 엔티티로부터 파생된 PK)에 대한 @GeneratedValue 사용은 지원되지 않는다.

생성 전략

전략설명
AUTOJPA 구현체가 적절한 전략을 자동 선택
IDENTITYDB의 auto_increment 사용 (MySQL 등)
SEQUENCEDB 시퀀스 사용 (Oracle, PostgreSQL 등)
TABLE별도의 키 생성 테이블 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
// 예제 1: 시퀀스 전략
@Id
@GeneratedValue(strategy=SEQUENCE, generator="CUST_SEQ")
@Column(name="CUST_ID")
public Long getId() { return id; }
// → "CUST_SEQ"라는 시퀀스로 ID 생성

// 예제 2: 테이블 전략
@Id
@GeneratedValue(strategy=TABLE, generator="CUST_GEN")
@Column(name="CUST_ID")
Long id;
// → "CUST_GEN"이라는 키 생성 테이블로 ID 생성

@MappedSuperclass


공통 필드를 상속시키기 위한 부모 클래스
이 클래스 자체는 테이블로 생성되지 않고, 자식 엔티티들이 필드를 물려받는다.

  • BaseEntity는 테이블이 생성되지 않음
  • 각 자식 엔티티가 id 필드와 @GeneratedValue 설정을 그대로 상속
  • 공통 필드(id, 생성일, 수정일 등)를 한 곳에서 관리할 수 있어 코드 중복 제거
1
2
3
4
5
6
7
8
9
10
11
12
13
// 매핑된 슈퍼클래스 (테이블로 생성되지 않음)
@MappedSuperclass
public abstract class BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;  // ← 여기에 @GeneratedValue 적용

    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    // getter, setter...
}
1
2
3
4
5
6
7
8
9
// 자식 엔티티 1
@Entity
@Table(name = "users")
public class User extends BaseEntity {
// id, createdAt, updatedAt 필드를 상속받음

    private String username;
    private String email;
}
1
2
3
4
5
6
7
8
9
// 자식 엔티티 2
@Entity
@Table(name = "products")
public class Product extends BaseEntity {
// id, createdAt, updatedAt 필드를 상속받음

    private String name;
    private int price;
}

@MapsId 파생 기본 키


예시

User와 UserProfile이 1:1 관계이고, UserProfile의 PK가 User의 PK를 그대로 사용

1
2
3
4
5
6
7
8
9
10
11
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;  // PK 자동 생성

    private String username;

    @OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
    private UserProfile profile;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
public class UserProfile {
@Id
private Long id;  // User의 id를 그대로 사용 (자동 생성 X)

    @OneToOne
    @MapsId  // ← User의 PK를 이 엔티티의 PK로 매핑
    @JoinColumn(name = "id")
    private User user;

    private String bio;
    private String profileImageUrl;
}

결과 테이블

users user_profile 
id (PK)usernameid (PK, FK)bio
1“kim”1“안녕하세요”
2“lee”2“반갑습니다”

UserProfile.id는 항상 User.id동일한 값

왜 @GeneratedValue를 사용할 수 없을까 ?

파생 기본 키는 이미 값이 정해져 있으므로 새로 생성할 필요가 없고, 생성하면 안됨

1
2
3
4
5
6
7
8
9
10
11
// 이렇게 하면 안 됨
@Entity
public class UserProfile {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)  // 충돌!
    private Long id;

    @OneToOne
    @MapsId
    private User user;
}
  • @GeneratedValue: “내가 새 값 만들게!” (예: 100)
  • @MapsId: “User의 id 값 써!” (예: 1)

→ 둘 다 적용하면: id가 100이어야 할지, 1이어야 할지 판단할 수 없음

 @GeneratedValue@MapsId
값의 출처DB가 자동 생성연관된 엔티티(User)로부터 복사
결정 시점INSERT 시 DB가 결정연관 관계 설정 시 결정

올바른 사용법

1
2
3
4
5
6
7
8
9
10
11
@Entity
public class UserProfile {
    @Id  // @GeneratedValue 없이 @Id만!
    private Long id;

    @OneToOne
    @MapsId  // User.id 값이 자동으로 이 엔티티의 id가 됨
    @JoinColumn(name = "id")
    private User user;

}
1
2
3
4
5
6
7
8
// 사용 예시
User user = new User();
user.setUsername("kim");
// user를 저장하면 id = 1 자동 생성

UserProfile profile = new UserProfile();
profile.setUser(user);  // 이 순간 profile.id도 1이 됨
profile.setBio("안녕하세요");

ID 생성 전략 관련 Hibernate 코드


주요 클래스

1. 전략 결정 및 생성기 선택

  • org.hibernate.id.factory.internal.StandardIdentifierGeneratorFactory
  • org.hibernate.id.IdentifierGenerator (인터페이스)

2. 각 전략별 구현 클래스

전략구현 클래스
IDENTITYorg.hibernate.id.IdentityGenerator
SEQUENCEorg.hibernate.id.enhanced.SequenceStyleGenerator
TABLEorg.hibernate.id.enhanced.TableGenerator
AUTO위 중 하나를 자동 선택

예시: IdentityGenerator 핵심 코드

1
2
3
4
5
6
7
8
9
10
// org.hibernate.id.IdentityGenerator (간략화)
public class IdentityGenerator implements IdentifierGenerator {

    @Override
    public Object generate(SharedSessionContractImplementor session, Object object) {
        // IDENTITY 전략은 DB가 값을 생성하므로
        // INSERT 후 생성된 값을 가져옴
        return IdentifierGeneratorHelper.POST_INSERT_INDICATOR;
    }
}

SimpleJpaRepository.save()부터 ID 생성까지 흐름


전체 흐름 개요

1
2
3
4
5
6
7
8
9
10
11
12
13
SimpleJpaRepository.save()
          ↓
EntityManager.persist()
          ↓
SessionImpl.persist()
          ↓
DefaultPersistEventListener
          ↓
AbstractSaveEventListener.saveWithGeneratedId()
          ↓
IdentifierGenerator.generate()
          ↓
INSERT 실행 & ID 반환

1. SimpleJpaRepository.save()

Spring Data JPA의 진입점. 새 엔티티인지 판단 후 persist 또는 merge 호출

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// org.springframework.data.jpa.repository.support.SimpleJpaRepository
@Transactional
@Override
public <S extends T> S save(S entity) {

    Assert.notNull(entity, "Entity must not be null");

    if (entityInformation.isNew(entity)) {
        // 새 엔티티면 persist
        entityManager.persist(entity);
        return entity;
    } else {
        // 기존 엔티티면 merge
        return entityManager.merge(entity);
    }
}

2. EntityManager.persist()

Hibernate Session 구현체. 이벤트 기반으로 persist 처리를 리스너에게 위임

1
2
3
4
5
6
// org.hibernate.internal.SessionImpl
@Override
public void persist(Object object) {
    checkOpen();
    firePersist(new PersistEvent(null, object, this));
}
1
2
3
4
private void firePersist(PersistEvent event) {
    // 이벤트 리스너에게 위임
    fastSessionServices.eventListenerGroup_PERSIST.fireEventOnEachListener(event, PersistEventListener::onPersist);
}

※ Hibernate에서의 이벤트

Session 내부에서 발생하는 영속성 행위(persist, merge, delete …)를 여러 리스너에게 위임하기 위한 구조적 패턴

  • persist() 시점에 해야 할 일들:
    • transient → persistent 상태 전이
    • identifier 생성 전략 처리
    • 1차 캐시에 엔티티 등록
    • cascade persist
    • entity 상태 검사
    • Interceptor, Listener 개입
    • flush 시점 작업 준비
  • 이걸 if-else 덩어리로 SessionImpl 안에 작성하면 확장성, 유지보수성이 매우 떨어짐
  • 그래서 Hibernate는:
    • “행위 = 이벤트”
    • “행위에 반응하는 로직 = 이벤트 리스너” 구조를 택함.

3. DefaultPersistEventListener

persist 이벤트 처리. 엔티티 상태(TRANSIENT, DETACHED 등)에 따라 분기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// org.hibernate.event.internal.DefaultPersistEventListener
@Override
public void onPersist(PersistEvent event) throws HibernateException {
    onPersist(event, IdentityHashMap.newInstance(10));
}

@Override
public void onPersist(PersistEvent event, PersistContext createdAlready) {
final Object object = event.getObject();

    // 엔티티 상태 확인
    final EntityEntry entityEntry = event.getSession()
        .getPersistenceContextInternal()
        .getEntry(object);

    EntityState entityState = EntityState.getEntityState(object, ...);

    switch (entityState) {
        case TRANSIENT:
            // 새 엔티티 → 저장 처리
            entityIsTransient(event, createdAlready);
            break;
        // ... 다른 상태들
    }
}

protected void entityIsTransient(PersistEvent event, PersistContext createdAlready) {
    // AbstractSaveEventListener의 메서드 호출
    saveWithGeneratedId(...);
}
HibernateJPA
TRANSIENTNew
PERSISTENTManaged
DETACHEDDetached
DELETEDRemoved

4. AbstractSaveEventListener.saveWithGeneratedId()

ID 생성기를 통해 ID를 생성하고 저장 수행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// org.hibernate.event.internal.AbstractSaveEventListener
protected Object saveWithGeneratedId(Object entity, String entityName, Object anything,
EventSource source, boolean requiresImmediateIdAccess) {

    // 엔티티 메타데이터 가져오기
    EntityPersister persister = source.getEntityPersister(entityName, entity);

    // ★ ID 생성기 가져와서 ID 생성
    Object generatedId = persister.getIdentifierGenerator()
        .generate((SharedSessionContractImplementor) source, entity);

    // IDENTITY 전략인 경우 (POST_INSERT_INDICATOR 반환됨)
    if (generatedId == IdentifierGeneratorHelper.POST_INSERT_INDICATOR) {
        // INSERT 후 ID를 가져와야 함
        return performSaveWithId(entity, null, persister, ...);
    }

    // SEQUENCE, TABLE 전략인 경우 (이미 ID가 생성됨)
    return performSaveWithId(entity, generatedId, persister, ...);
}

5. IdentifierGenerator 구현체들

각 전략에 맞게 실제 ID 값을 생성

IDENTITY

1
2
3
4
5
6
7
8
9
// org.hibernate.id.IdentityGenerator
public class IdentityGenerator implements IdentifierGenerator {

    @Override
    public Object generate(SharedSessionContractImplementor session, Object object) {
        // DB가 ID를 생성하므로 "나중에 가져올게" 표시만 반환
        return IdentifierGeneratorHelper.POST_INSERT_INDICATOR;
    }
}

SEQUENCE

1
2
3
4
5
6
7
8
9
10
11
12
// org.hibernate.id.enhanced.SequenceStyleGenerator
public class SequenceStyleGenerator implements IdentifierGenerator {

    @Override
    public Object generate(SharedSessionContractImplementor session, Object object) {
        // DB 시퀀스에서 다음 값 조회
        // SELECT nextval('hibernate_sequence')
        return optimizer.generate(
            accessCallback  // 실제 시퀀스 호출
        );
    }
}

TABLE 전략

1
2
3
4
5
6
7
8
9
10
// org.hibernate.id.enhanced.TableGenerator
public class TableGenerator implements IdentifierGenerator {

    @Override
    public Object generate(SharedSessionContractImplementor session, Object object) {
        // 키 생성 테이블에서 값 조회 및 업데이트
        // SELECT, UPDATE 실행
        return optimizer.generate(accessCallback);
    }
}

6. INSERT 실행 (IDENTITY 전략의 경우)

실제 INSERT SQL 실행 후 DB가 생성한 ID를 조회하여 반환

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// org.hibernate.id.insert.AbstractReturningDelegate
public Object performInsert(PreparedStatementDetails insertStatementDetails,
JdbcValueBindings jdbcValueBindings, Object entity, SharedSessionContractImplementor session) {

    // INSERT 실행
    session.getJdbcCoordinator()
        .getResultSetReturn()
        .executeUpdate(insertStatement);

    // DB에서 생성된 ID 조회
    // MySQL: SELECT LAST_INSERT_ID()
    // PostgreSQL: RETURNING id
    ResultSet rs = getInsertGeneratedIdentifierDelegate()
        .executeAndExtract(insertStatement, session);

    return rs.getLong(1);  // 생성된 ID 반환
}

7. 엔티티에 ID 설정

1
2
3
4
5
6
7
8
9
10
// org.hibernate.event.internal.AbstractSaveEventListener
protected Object performSaveOrReplicate(Object entity, Object id, EntityPersister persister, ...) {

    // 생성된 ID를 엔티티에 설정
    persister.setIdentifier(entity, id, session);

    // 영속성 컨텍스트에 등록
    source.getPersistenceContextInternal()
        .addEntity(entity, Status.MANAGED, ...);
}

전략별 SQL 실행 시점 비교

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────┬──────────────────────────────────────────────┐
│   전략      │                실행 흐름                      │
├─────────────┼──────────────────────────────────────────────┤
│ IDENTITY    │ generate() → INSERT → SELECT LAST_INSERT_ID  │
│             │ (ID는 INSERT 후에 알 수 있음)                 │
├─────────────┼──────────────────────────────────────────────┤
│ SEQUENCE    │ SELECT nextval() → generate() → INSERT       │
│             │ (ID를 먼저 조회 후 INSERT)                    │
├─────────────┼──────────────────────────────────────────────┤
│ TABLE       │ SELECT & UPDATE → generate() → INSERT        │
│             │ (키 테이블에서 ID 확보 후 INSERT)             │
└─────────────┴──────────────────────────────────────────────┘

디버깅 브레이크포인트 추천 위치

1
2
3
4
5
1. SimpleJpaRepository.save()              // 진입점
2. SessionImpl.persist()                   // Hibernate 진입
3. AbstractSaveEventListener.saveWithGeneratedId()  // ★ 핵심
4. IdentityGenerator.generate()            // 전략별 분기
5. AbstractReturningDelegate.performInsert() // 실제 INSERT

IDENTITY 전략에서 Bulk Insert가 안 되는 이유


IDENTITY 전략은 INSERT를 실행해야만 ID를 알 수 있기 때문에, Hibernate가 batch insert를 비활성화한다.

1. ActionQueue에서 batch 가능 여부 판단

1
2
3
4
5
6
7
8
9
10
11
12
// org.hibernate.engine.spi.ActionQueue
public void addAction(EntityInsertAction action) {

    // ★ 핵심: ID 생성 방식 확인
    if (action.isEarlyInsert()) {
        // IDENTITY 전략 → 즉시 INSERT 실행해야 함
        executeInsertNow(action);
    } else {
        // SEQUENCE, TABLE → 나중에 batch로 모아서 실행 가능
        addToInsertQueue(action);
    }
}

2. isEarlyInsert() 판단 로직

1
2
3
4
5
// org.hibernate.action.internal.EntityInsertAction
public boolean isEarlyInsert() {
  // ID 생성기가 POST_INSERT 타입인지 확인
  return persister.getIdentifierGenerator() instanceof PostInsertIdentifierGenerator;
}
1
2
3
4
5
6
// org.hibernate.id.IdentityGenerator
public class IdentityGenerator implements PostInsertIdentifierGenerator {  // ← 이 인터페이스!

    // IDENTITY는 PostInsertIdentifierGenerator를 구현
    // = INSERT 후에야 ID를 알 수 있다는 의미
}

3. Batch 비활성화

1
2
3
4
5
6
7
8
9
10
11
// org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl
public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) {

    // IDENTITY 전략이면 batch size를 1로 강제
    if (isIdentityInsert(key)) {
        return new NonBatchingBatch(key, jdbcCoordinator);
    }

    // 다른 전략은 설정된 batch size 사용
    return new BatchingBatch(key, jdbcCoordinator, batchSize);
}
1
2
3
4
5
6
7
8
9
10
// org.hibernate.id.insert.AbstractReturningDelegate
public Object performInsert(...) {

    // IDENTITY: 각 INSERT마다 즉시 실행하고 ID 조회
    jdbcCoordinator.getResultSetReturn().executeUpdate(insertStatement);

    // 생성된 ID 즉시 조회 (이게 batch를 막는 원인)
    ResultSet rs = statement.getGeneratedKeys();
    return rs.getLong(1);
}

예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
┌─────────────────────────────────────────────────────────────────┐
│                    SEQUENCE 전략 (Batch 가능)                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. SELECT nextval() → ID = 1                                   │
│  2. SELECT nextval() → ID = 2                                   │
│  3. SELECT nextval() → ID = 3                                   │
│                                                                 │
│  이 시점에 모든 엔티티의 ID를 알고 있음!                          │
│  → 영속성 컨텍스트에 등록 가능                                    │
│  → 연관관계 설정 가능                                            │
│                                                                 │
│  4. INSERT 3개를 한 번에 batch 실행                              │
│     INSERT INTO user (id, name) VALUES (1, 'A'), (2, 'B')...    │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                    IDENTITY 전략 (Batch 불가)                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. INSERT user1 → ID = ? (모름)                                │
│                                                                 │
│     문제: ID를 모르면...                                      │
│        - 영속성 컨텍스트에 등록 불가 (1차 캐시 key가 없음)        │
│        - 연관관계 FK 설정 불가                                   │
│        - 다음 엔티티 처리 불가                                   │
│                                                                 │
│     → INSERT 실행하고 LAST_INSERT_ID() 조회해야 함               │
│     → 그래야 다음 단계 진행 가능                                 │
│                                                                 │
│  2. INSERT user2 → 마찬가지로 즉시 실행 필요                     │
│  3. INSERT user3 → 마찬가지                                     │
│                                                                 │
│  결과: 각각 개별 INSERT 실행 (batch 불가능)                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

영속성 컨텍스트 등록 시 ID 필요

1
2
3
4
5
6
// org.hibernate.engine.internal.StatefulPersistenceContext
public void addEntity(EntityKey key, Object entity) {
    // ★ EntityKey 생성에 ID가 필수!
    // ID가 없으면 1차 캐시에 등록 자체가 불가능
    entitiesByKey.put(key, entity);
}
1
2
3
4
5
// org.hibernate.engine.spi.EntityKey
public EntityKey(Object id, EntityPersister persister) {
    this.identifier = id;  // ← ID가 반드시 있어야 함
    this.persister = persister;
}

연관관계에서의 문제

1
2
3
4
5
6
7
8
9
@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> items;
}
1
2
3
4
5
6
7
8
9
10
@Entity
public class OrderItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "order_id")
    private Order order;  // ← FK로 Order.id 필요!
}
1
2
3
4
5
6
7
8
// 저장 시
Order order = new Order();
OrderItem item1 = new OrderItem();
OrderItem item2 = new OrderItem();
order.addItem(item1);
order.addItem(item2);

entityManager.persist(order);
1
2
3
4
5
6
7
8
9
10
11
1. INSERT order → ID 조회 (예: 100)
   → order.id = 100 설정
   → 영속성 컨텍스트에 등록

2. INSERT orderItem1 (order_id = 100) → ID 조회
   → Order.id를 알아야 FK 설정 가능!

3. INSERT orderItem2 (order_id = 100) → ID 조회

∴ Order INSERT가 먼저 완료되어야 OrderItem INSERT 가능
∴ Batch로 묶을 수 없음

정리

전략ID 획득 시점Batch 가능이유
IDENTITYINSERT 후XID를 몰라서 영속성 컨텍스트 등록, FK 설정 불가
SEQUENCEINSERT 전O미리 ID 확보 → 모아서 batch 실행 가능
TABLEINSERT 전O미리 ID 확보 → 모아서 batch 실행 가능

결론: IDENTITY는 “INSERT 해봐야 ID를 안다”는 근본적 특성 때문에, Hibernate가 아무리 최적화해도 batch insert가 불가능하다.

※ 참고 : Hibernate 6.6 BulkInsertionCapableIdentifierGenerator


IdentityGenerator (Hibernate 6.6.x)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class IdentityGenerator
implements PostInsertIdentifierGenerator,
BulkInsertionCapableIdentifierGenerator,  // ← 이게 있어서 bulk 가능?
StandardGenerator {

    public boolean referenceColumnsInSql(Dialect dialect) {
        return dialect.getIdentityColumnSupport().hasIdentityInsertKeyword();
    }

    public String[] getReferencedColumnValues(Dialect dialect) {
        return new String[]{dialect.getIdentityColumnSupport().getIdentityInsertString()};
    }

    // ... 나머지 코드
}

결론부터 말하면

  1. BulkInsertionCapableIdentifierGenerator는 HQL/JPQL의 INSERT ... SELECT 벌크 구문을 IDENTITY 테이블에서도 사용할 수 있게 해주는 인터페이스
  2. 일반적인 persist() 호출의 JDBC batch insert와는 무관
  3. 따라서 IDENTITY 전략에서 persist() 루프의 batch insert는 여전히 불가능
  • 즉, 여전히 일반적인 entityManager.persist() 루프에서는 batch insert가 안됨

BulkInsertionCapableIdentifierGenerator 인터페이스 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// org.hibernate.id.BulkInsertionCapableIdentifierGenerator
public interface BulkInsertionCapableIdentifierGenerator extends IdentifierGenerator {

    /**
     * SQL 구문에서 ID 컬럼을 참조해야 하는지?
     * (INSERT 구문에 ID 컬럼을 명시적으로 포함할지 여부)
     */
    boolean referenceColumnsInSql(Dialect dialect);

    /**
     * ID 컬럼에 들어갈 값 (키워드 등)
     * 예: Oracle의 "DEFAULT", SQL Server의 "DEFAULT" 등
     */
    String[] getReferencedColumnValues(Dialect dialect);
}

이 인터페이스의 실제 용도

HQL/JPQL의 bulk insert 구문 지원

1
2
3
4
String hql = "INSERT INTO UserArchive (id, name, email) " +
"SELECT u.id, u.name, u.email FROM User u WHERE u.status = 'INACTIVE'";

entityManager.createQuery(hql).executeUpdate();
  • 생성되는 SQL:sql
1
2
3
-- IDENTITY 컬럼이 있어도 bulk INSERT ... SELECT 가능하게 함
INSERT INTO user_archive (id, name, email)
SELECT id, name, email FROM user WHERE status = 'INACTIVE'

코드 분석: referenceColumnsInSql()

1
2
3
4
// IdentityGenerator
public boolean referenceColumnsInSql(Dialect dialect) {
    return dialect.getIdentityColumnSupport().hasIdentityInsertKeyword();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// 예: SQL Server의 IdentityColumnSupport
public class SQLServerIdentityColumnSupport extends IdentityColumnSupportImpl {

    @Override
    public boolean hasIdentityInsertKeyword() {
        return true;  // SQL Server는 IDENTITY_INSERT 키워드 있음
    }

    @Override
    public String getIdentityInsertString() {
        return "default";  // INSERT 시 'DEFAULT' 키워드 사용
    }
}
1
2
3
4
5
6
7
8
// MySQL의 경우
public class MySQLIdentityColumnSupport extends IdentityColumnSupportImpl {

    @Override
    public boolean hasIdentityInsertKeyword() {
        return false;  // MySQL은 없음
    }
}

정리: 두 가지 “Bulk Insert”의 차이

구분JDBC Batch InsertHQL Bulk Insert
사용법persist() 여러 번INSERT INTO ... SELECT
관련 인터페이스없음 (batch 설정)BulkInsertionCapableIdentifierGenerator
IDENTITY에서XO
영속성 컨텍스트각 엔티티 관리됨관리 안 됨 (벌크 연산)
1
2
3
4
// JDBC Batch - IDENTITY에서 불가
for (User u : users) {
em.persist(u);  // 개별 INSERT
}
1
2
// HQL Bulk - IDENTITY에서 가능 (BulkInsertionCapableIdentifierGenerator 덕분)
em.createQuery("INSERT INTO Archive SELECT u FROM User u").executeUpdate();
This post is licensed under CC BY 4.0 by the author.