Java & Spring

JPA의 자동 키 생성 전략

ju_young 2024. 1. 5. 23:23
728x90

JPA는 데이터베이스 테이블 대체 키를 기본 키로 자동 생성하는 기능을 지원한다. 다시 말해 어떤 하나의 필드 값을 기본 키로 지정해준다.

@Entity
class Team {
    @Id
    @GeneratedValue
    private Long id;
}

 

위와 같이 @Id@GeneratedValue를 추가해주면 되는데 @GeneratedValue는 다음 4가지 전략으로 나누어진다.

 

Strategy Description
AUTO (default) 자동으로 생성 전략을 결정
IDENTITY 기본키 생성을 데이터베이스에 위임
SEQUENCE 시퀀스 오브젝트를 사용하여 기본키 생성
TABLE 키 생성 전용 데이블을 만들고 이를 사용하여 기본키 생성

AUTO 생성 전략

  • 데이터 타입이 UUID라면 UUID Generator를 선택
  • 데이터 타입이 Numeral(Integer, Long)이라면 hibernate.use-new-id-generator-mappings 속성 값을 확인
    • use-new-id-generator-mappings가 false라면 Native Generator를 선택
    • use-new-id-generator-mappings가 true라면 SequenceStyleGenerator을 선택하지만 데이터베이스가 Sequence를 지원하지 않는다면 Table Generator를 선택 (MySQL이 Sequence를 지원하지 않음)

application 설정

use-new-id-generator-mappings는 default가 true이다. false로 수정하려면 다음과 같이 값을 지정해주면 된다.

spring:
    jpa:
        hibernate:
            use-new-id-generator-mappings: false

NOTE
Spring Boot 3.X 부터는 use-new-id-generator-mappings가 지원되지 않는다.

SEQUENCE Generator

처음 애플리케이션을 실행하면 다음과 같이 sequence가 생성된다.

Hibernate: create sequence hibernate_sequence start with 1 increment by 1

sequence는 순서대로 번호를 생성해주는 객체이며 위에서는 "1부터 시작하여 1씩 증가하는 번호를 생성해주는 sequence를 생성"했다고 직관적으로 해석할 수 있다.

 

실제로 데이터를 삽입하게되면 다음과 같이 생성된 sequence로부터 값을 호출하는 것을 확인할 수 있었다.

Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
...

Table Generator

MySQL에서는 Sequence 객체가 지원되지 않아 다음과 같이 hibernate_sequence라는 테이블을 생성한다.

Hibernate: create table hibernate_sequence (next_val bigint) engine=InnoDB


next_val이라는 attribute는 하나의 튜플을 가지며 데이터가 삽입될 때마다 증가한다. 예를 들어서 25개의 데이터가 삽입되었다면 next_val은 26이 될 것이다.

 

하지만 이렇게 테이블을 따로 생성할 경우 다음과 같이 매번 삽입할 때마다 쿼리가 select문이 적용된다.

Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
...

 

이때 select문의 뒤에 for update가 붙은 것을 보아 locking read가 적용된 것을 확인할 수 있다. 그리고 이처럼 serial하게 실행되기때문에 병목 현상이 일어날 수 있다. 실제로 Sequence와 Table의 성능 비교를 한 결과 다음과 같다고 한다.


실행 스레드 수가 증가할 수록 느려지는 것을 확인할 수 있다. 따라서 Identity 또는 Sequence를 잘 활용하는 것이 좋다.

IDENTITY

IDENTITY 전략은 데이터베이스가 대신 키 생성을 하게 만드는 것이다. 예를 들어 MySQL은 AUTO_INCREMENT를 사용하여 순차적으로 값이 증가하는 숫자를 부여한다. 실제로 애플리케이션을 실행할때 테이블 생성시 기본 키에 auto_increment라고 지정해주는 것을 확인할 수 있었다.

Hibernate: create table team (id bigint not null auto_increment, ... , primary key (id)) engine=InnoDB

 

IDENTITY의 큰 단점은 hibernate에서 batch insert가 지원이 안된다는 것이다.

Batch Insert가 안되는 이유

증가 프로세스는 트랜잭션 바깥에서 일어난다. 만약 다음 할당할 id가 5이고 10개의 데이터를 batch insert를 한다면 마지막 id는 14이고 이후 id는 15가 될 것이다. 하지만 이때 batch insert한 결과를 롤백한다면 마지막 데이터의 id는 4가 될 테고 다음 할당할 id는 15가 되어 값의 갭 차이가 커질 것이다.

 

유일한 단점은 batch insert가 모두 진행되기 전에 다음 insert는 새로 할당받을 값을 알 수 없다는 것이다. 그리고 이런 제한은 HIbernate가 채택한 Tranditional write-behind 전략을 방해한다고 한다.

 

그래도 IDENTITY를 사용하여 batch insert를 수행해야할 경우 JDBC를 사용할 수 있다.

 

(관련 글을 읽고 나름대로 생각해서 작성한 글이어서 틀린 부분이 있을 수 있다.)

NOTE
Hibernate는 Persistence Context를 가능한한 마지막에 flush한다. 그리고 이러한 전략을 Tranditional write-behind라고 한다.


[reference]
https://stackoverflow.com/questions/27697810/why-does-hibernate-disable-insert-batching-when-using-an-identity-identifier-gen
https://vladmihalcea.com/why-you-should-never-use-the-table-identifier-generator-with-jpa-and-hibernate/
https://hackmd.io/@bonjugi/rJMPlcwTN
https://www.popit.kr/%ED%95%98%EC%9D%B4%EB%B2%84%EB%84%A4%EC%9D%B4%ED%8A%B8%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%9E%90%EB%8F%99-%ED%82%A4-%EC%83%9D%EC%84%B1-%EC%A0%84%EB%9E%B5%EC%9D%84-%EA%B2%B0%EC%A0%95%ED%95%98/

728x90