[JPA] ID Generation

2025. 3. 1. 00:08·Spring Framework/JPA
반응형

JPA로 엔티티를 설계할 때 고민해야하는 요소 중 하나는 식별자를 어떻게 생성할 것인가이다. 처음에는 @GeneratedValue 정도만 쓰고 지나가지만, 큰 규모의 시스템이나 대량 데이터를 고려한다면 방버에 따라 성능·DB 구조·운영 방식에까지 영향을 줄 수 있다. 특히 PostgreSQL 환경에서는 ID 전략 선택이 Batch Insert 성능, 트랜잭션 처리 방식, 스케일아웃 구조와 직접 연결된다.

JPA 엔티티의 식별자 속성에 @Id 애너테이션은 중요한 역할을 한다. 영속성 컨텍스트가 엔티티를 1차 캐시에 저장할 때 키로 사용하는 값이 바로 ID이다. 또한 Dirty Checking의 기준이 되고 엔티티의 생명주기 관리의 단위가 되기 때문에, ID 생성 전략은 단순한 옵션이 아니라 엔티티의 전체 흐름을 결정하는 요소로 볼 수 있다.

JPA는 @GeneratedValue어노테이션을 제공해 다양한 ID 생성 방식을 선택할 수 있다. 여기서 지정한 방식에 따라 영속성 컨텍스트가 엔티티 ID를 언제, 어떻게 생성할지 결정된다.

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
}

지정할 수 있는 생성 전략은 아래와 같다.

  • AUTO : JPA 구현체가 DB에 맞게 자동으로 선택한다(JPA/DB)
  • IDENTIY : DB의 AUTO_INCREMENT 사용(DB)
  • SEQUENCE : DB 시퀀스 오브젝트 사용(DB)
  • TABLE : 별도 테이블로 ID 관리(JPA)

실무에 주로 PostgreSQL을 사용 중이기 때문에 PostgreSQL을 위주로 정리했다. PostgreSQL은 시퀀스를 기본적으로 지원하며, SERIAL/BIGSERIAL 역시 내부적으로 시퀀스 오브젝트를 생성한다.

-- SERIAL 타입 사용 시 자동 생성되는 시퀀스
CREATE TABLE product (
    id SERIAL PRIMARY KEY  -- product_id_seq 시퀀스 자동 생성
);

PostgreSQL에서의 생성전략

GenerationType.IDENTITY

PostgreSQL의 SERIAL, BIGSERIAL을 사용해 DB가 ID를 생성하는 방식이다.

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

DDL(auto) 결과:

CREATE TABLE product (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255)
);
  • DB가 INSERT 시점에 ID를 생성한다 : 엔티티의 ID는 insert 후에야 결정됨
  • persist() 호출 시 JPA는 즉시 INSERT를 실행해야 한다 : Batch Insert 불가능
  • 구조가 단순하고 이해하기 쉽다.
Product product = new Product("상품A");
entityManager.persist(product);
// 즉시 INSERT 실행
// INSERT INTO product (name) VALUES ('상품A') RETURNING id;

GenerationType.SEQUENCE

데이터베이스 시퀀스 오브젝트에서 ID를 미리 가져와 엔티티에 할당한다.

@Entity
@SequenceGenerator(
    name = "product_seq_generator",
    sequenceName = "product_seq",
    allocationSize = 50
)
public class Product {
    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "product_seq_generator"
    )
    private Long id;

    private String name;
}

DDL(auto):

CREATE SEQUENCE product_seq START WITH 1 INCREMENT BY 50;
CREATE TABLE product (
    id BIGINT PRIMARY KEY,
    name VARCHAR(255)
);

allocationSize = 50은 ****다음과 같이 동작한다.

  1. 첫 번째 시퀀스 호출 시 DB에서 1을 받아옴
  2. JPA는 메모리에 1~50까지 할당 (50개 확보)
  3. 이후 persist 과정에서는 DB 호출 없이 ID 증가
  4. 50개를 모두 사용하면 다시 DB에서 51을 받아와 51~100 할당

주요 특징은 아래와 같다.

  • persist() 시점에 ID 값이 이미 있다 : Batch Insert 가능
  • 네트워크 round-trip이 감소해 성능 유리하다.
  • 시퀀스 기반 DB(PostgreSQL, Oracle 등)에서만 사용 가능하다.
  • 시퀀스 INCREMENT 값과 allocationSize가 맞지 않으면 ID gap 발생할 수 있다.

예시:

entityManager.persist(new Product("A")); // ID = 1
entityManager.persist(new Product("B")); // ID = 2

entityManager.flush();
// 일괄 INSERT

GenerationType.TABLE

ID 값을 저장하는 별도 테이블을 사용한다.

@Entity
@TableGenerator(
    name = "product_table_generator",
    table = "id_generator", // table 이름을 지정한다.
    pkColumnName = "gen_name",
    valueColumnName = "gen_value",
    pkColumnValue = "product_seq",
    allocationSize = 50
)
public class Product {
    @Id
    @GeneratedValue(
        strategy = GenerationType.TABLE,
        generator = "product_table_generator"
    )
    private Long id;

    private String name;
}

DDL:

CREATE TABLE id_generator (
    gen_name VARCHAR(255) PRIMARY KEY,
    gen_value BIGINT
);
INSERT INTO id_generator VALUES ('product_seq', 0);

모든 데이터베이스에서 사용할 수 있으며 allocationSize 최적화가 가능하다. 하지만 테이블 락 및 추가 시 SELECT … FOR UPDATE 쿼리가 필요하기 때문에 성능이 가장 낮다. 또한 여러 트랜잭션이 동시에 접근할 경우 동시성 이슈가 발생할 수 있어 사용을 권장하지 않는다.

-- ID 획득 시
SELECT gen_value FROM id_generator WHERE gen_name = 'product_seq' FOR UPDATE;
UPDATE id_generator SET gen_value = gen_value + 50 WHERE gen_name = 'product_seq';

GenerationType.AUTO

GenerationType.AUTO는 JPA 구현체(Hibernate)가 데이터베이스 방언(Dialect)에 따라 가장 적절한 ID 생성 전략을 자동으로 선택하는 방식이다.

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
}

AUTO 자체적으로는 추가 설정이 필요 없으며, Hibernate가 알아서 ID 전략을 선택한다.

Hibernate는 DB Dialect를 보고 다음 중 하나를 선택한다.

  • PostgreSQL → 대부분 SEQUENCE 사용
  • MySQL → 시퀀스가 없으므로 IDENTITY 사용
  • Oracle → SEQUENCE 사용
  • H2 (테스트 DB) → Dialect에 따라 SEQUENCE 또는 IDENTITY

즉, 동일한 애플리케이션이라도 운영 DB와 테스트 DB의 Dialect가 다르면 ID 전략이 달라질 수 있다.

설정이 간단하고 편리하지만 DB에 따라 적용되는 전략이 달라지기 때문에 주의해야한다. 가령 운영이나 개발 환경과 달리 테스트 시에는 h2를 사용하는 경우가 있는데, 전략 불일치로 문제가 발생할 수 있다.

앞서 살펴본 것 처럼 생성 전략이 바뀌면 성능 특성이 달라질 수 있기 때문에(ex: Batch Insert) 프로토 타입 단계가 아니라면 SEQUENCE 또는 IDENTITY를 명시적으로 지정하는 것을 권장한다.


ID 생성 전략은 처음엔 단순해 보이지만 실제 서비스에 적용하려고 하면 생각보다 많은 고민이 따라온다. 대량 INSERT가 필요한 상황에서는 어떤 전략이 가장 빠른지, allocationSize는 얼마나 줘야 하는지, 쿠폰처럼 비즈니스 규칙이 들어간 코드는 언제 만들어야 하는지, 멀티 리전 환경에서는 UUID가 맞는지 SEQUENCE가 맞는지… 이런 질문들이 계속 이어진다. 어떤 전략을 쓰느냐에 따라 Batch Insert가 가능해지기도 하고 불가능해지기도 하니, 내부 동작까지 알아야 판단할 수 있는 경우도 많다. 어떤 상황이 주어지더라도 적절한 선택을 할 수 있게 좀 더 공부하고 정리해야 겠다.

반응형
저작자표시 비영리 변경금지 (새창열림)
'Spring Framework/JPA' 카테고리의 다른 글
  • [JPA] 엔티티의 equals()와 hashCode()
  • [JPA] N+1 문제
  • [JPA] 5개 데이터가 4+1개 쿼리로 나누어지다.
  • [JPA] Batch Insert
덴마크초코우유
덴마크초코우유
IT, 알고리즘, 프로그래밍 언어, 자료구조 등 정리
    반응형
  • 덴마크초코우유
    이것저것끄적
    덴마크초코우유
  • 전체
    오늘
    어제
    • 분류 전체보기 (134) N
      • Spring Framework (16)
        • Spring (8)
        • JPA (6)
        • Spring Security (0)
      • Language (51)
        • Java (11)
        • Python (10)
        • JavaScript (5)
        • NUXT (2)
        • C C++ (15)
        • PHP (8)
      • DB (16)
        • MySQL (10)
        • Reids (3)
        • Memcached (2)
      • 개발 (3)
      • 프로젝트 (7) N
      • Book (2)
      • PS (15)
        • 기타 (2)
        • 백준 (2)
        • 프로그래머스 (10)
      • 딥러닝 (8)
        • CUDA (0)
        • Pytorch (0)
        • 모델 (0)
        • 컴퓨터 비전 (4)
        • OpenCV (1)
      • 기타 (16)
        • 디자인패턴 (2)
        • UnrealEngine (8)
        • ubuntu (1)
        • node.js (1)
        • 블로그 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 미디어로그
    • 위치로그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    php
    딥러닝
    게임
    redis
    memcached
    MySQL
    JS
    클래스
    Unreal
    파이썬
    웹
    select
    게임 개발
    pytorch
    프로그래머스
    NUXT
    JavaScript
    언리얼엔진4
    mscoco
    블루프린트
    Python
    FPS
    map
    알고리즘
    C++
    CPP
    PS
    C
    Unreal Engine
    자바
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
덴마크초코우유
[JPA] ID Generation
상단으로

티스토리툴바