본문 바로가기

JPA

[JPA] ID 생성 전략

✏️ @GeneratedValue

JPA에서 ID를 자동 생성하는 방법에는 4가지 전략이 있다.
각 전략은 @GeneratedValue(strategy = GenerationType.XXX)로 지정할 수 있다.

 

📌 1. AUTO (기본값)

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

 

✅ 특징

  • 기본값으로 사용됨 (명시하지 않으면 AUTO가 적용됨).
  • 데이터베이스에 따라 적절한 전략을 자동 선택.
  • 보통 MySQL은 IDENTITY, H2나 Oracle은 SEQUENCE로 자동 선택됨.

🛠 예제: MySQL vs H2

데이터베이스 AUTO 전략이 선택하는 기본값
MySQL, MariaDB IDENTITY (AUTO_INCREMENT 사용)
H2, Oracle SEQUENCE (hibernate_sequence 자동 생성)

⚠️ 주의

  • 개발 환경과 운영 환경의 데이터베이스가 다르면 AUTO 전략이 다른 방식으로 동작할 수 있음.
  • 운영 환경에서 예상과 다르게 SEQUENCE가 적용되면 hibernate_sequence 테이블이 자동 생성될 수 있음.
  • 명확한 ID 전략을 원하면 IDENTITY, SEQUENCE, TABLE을 직접 설정하는 것이 좋음.

📌 2. IDENTITY (AUTO_INCREMENT)

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

 

✅ 특징

  • 데이터베이스가 ID 값을 자동 생성 (AUTO_INCREMENT 사용).
  • INSERT 실행 후 DB가 생성한 ID 값을 가져옴.
  • MySQL, MariaDB에서 주로 사용.

🔹 동작 방식

  1. persist() 호출 → 즉시 INSERT 실행
  2. DB가 AUTO_INCREMENT로 ID 값을 생성
  3. Hibernate가 생성된 ID를 조회해서 엔티티에 설정

💡 트랜잭션이 끝나기 전에 INSERT 실행됨(즉시 반영).

 

🛠 예제

Member member = new Member();
member.setName("John");
em.persist(member); // 여기서 바로 INSERT 실행됨

 

🔹 실행되는 SQL

INSERT INTO member (name) VALUES ('John');
SELECT LAST_INSERT_ID();
  • SELECT LAST_INSERT_ID()로 방금 생성된 ID를 가져옴

⚠️ 주의

  • JPA의 persist()를 호출하면 즉시 INSERT가 실행됨 → flush()가 필요 없음.
  • 배치 처리(벌크 INSERT)에 불리함 (한 번에 여러 개 INSERT가 어려움).
  • ID 값을 DB에서 직접 생성하기 때문에 영속성 컨텍스트에서 ID를 미리 알 수 없음.

📌 3. SEQUENCE (시퀀스 객체 사용)

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@SequenceGenerator(
    name = "member_seq_generator",
    sequenceName = "member_seq", // 매핑할 시퀀스 이름
    allocationSize = 1 // 기본값 50, 보통 1로 설정
)
private Long id;

✅ 특징

  • DB의 시퀀스를 사용하여 ID를 미리 가져옴.
  • @SequenceGenerator를 사용하여 원하는 시퀀스를 설정 가능.
  • Oracle, PostgreSQL에서 주로 사용.

🔹 동작 방식

  1. persist() 호출
  2. Hibernate가 NEXTVAL을 호출해서 미리 ID 값을 가져옴
  3. 엔티티에 ID를 설정한 후 INSERT 실행 (트랜잭션 커밋 시점)

💡 persist() 시점에서는 INSERT가 실행되지 않고, ID만 미리 할당됨!

 

🛠 예제

Member member = new Member();
member.setName("Alice");
em.persist(member); // 여기서는 INSERT 실행 안 됨 (ID만 미리 가져옴)

 

🔹 실행되는 SQL

SELECT NEXTVAL('member_seq'); -- ID 미리 가져오기
INSERT INTO member (id, name) VALUES (1, 'Alice'); -- 트랜잭션 커밋 시점에 실행됨

 

⚠️ 주의

  • allocationSize = 50이 기본값이므로, JPA가 50개씩 미리 ID를 가져와서 캐싱함.
  • 동시성 처리에 유리 (IDENTITY보다 성능이 좋음).
  • MySQL에서는 시퀀스를 지원하지 않으므로 사용 불가능.

📌 4. TABLE (키 생성 전용 테이블 사용)

@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@TableGenerator(
    name = "member_table_generator",
    table = "id_table", // ID를 저장할 테이블 이름
    pkColumnName = "entity", // 엔티티 이름을 저장하는 컬럼
    valueColumnName = "next_id", // 다음 ID 값을 저장하는 컬럼
    pkColumnValue = "member", // 엔티티 구분값
    allocationSize = 1
)
private Long id;

 

✅ 특징

  • 별도의 테이블을 만들어서 ID 값을 관리 (DBMS에 상관없이 사용 가능).
  • 모든 DB에서 동작하지만 성능이 가장 느림.

🔹 동작 방식

  1. persist() 호출
  2. ID를 관리하는 테이블 (id_table)에서 다음 ID 값을 조회
  3. 새로운 ID 값을 member 테이블에 INSERT

💡 JPA가 직접 ID를 관리하므로, 모든 DB에서 동작 가능.

 

🛠 예제

SELECT next_id FROM id_table WHERE entity = 'member'; -- 다음 ID 값 가져오기
UPDATE id_table SET next_id = next_id + 1 WHERE entity = 'member'; -- ID 값 증가
INSERT INTO member (id, name) VALUES (1, 'Bob'); -- 실제 데이터 저장

 

⚠️ 주의

  • 성능이 가장 느림 (매번 ID 조회 및 업데이트가 필요함).
  • 보통 SEQUENCE를 지원하지 않는 DB에서 사용하지만, 권장되지 않음.