[JPA] 기본키 매핑 @GeneratedValue의 사용법과 종류

3 분 소요

오늘은 Spring Data JPA 기본키 매핑하는 방법에 대해서 알아보겠습니다. JPA가 제공하는 데이터베이스 기본 키 생성 전략은 다음과 같습니다. 이때, 기본키를 할당하는 방법으로는 두가지가 있습니다.

  • 직접할당 : 기본 키를 어플리케이션에서 직접 할당 해주는 방법 (application에서 생성)
  • 자동생성 : 데이터베이스가 자동으로 할당해주는 방법 (db가 생성)

직접할당의 경우, @Id만 사용하면 됩니다. 자동 생성의 경우, 여러 가지 설정들이 있습니다. 데이터 베이스 방언들에 따라서 사용할 종류가 나눠지는 것 같습니다.


자동 생성의 종류

자동 생성은 @IDENTITY, SEQUENCE, TABLE, AUTO가 있습니다.

  • IDENTITY: 기본 키 생성을 데이터베이스에 위임한다. MySQL에서 많이 씁니다.
  • SEQUENCE: 데이터 베이스 오브젝트를 이용합니다. 데이터베이스 시퀀스를 사용해서 기본 키를 할당합니다. 추가적인 어노테이션으로 @SequenceGenerator 필요합니다.ORACLE에서 많이 사용하고, 사용방법은 밑에서 설명하겠습니다.
  • TABLE: 별도의 키 생성 테이블을 사용합니다. 모든 DB에서 사용가능합니다. @TableGenerator 필요합니다. ddl-auto가 아닌 경우, 별도의 Table을 생성해야 합니다.
  • AUTO: 방언에 따라 자동으로 기본 값으로 지정합니다.

IDENTITY

// IDENTITY 매핑 코드
@Entity
public class Member{

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

@Id, @GeneratedValue(strategy = GenerationType.IDENTITY)를 입력해주면 됩니다. IDENTITY 전략은 데이터베이스에 값을 저장하고 나서야 기본 키 값을 구할 수 있습니다.

아래의 코드를 보면 transaction.commit(); 커밋 코드가 없음에도 출력이 되는 것을 알 수 있습니다.

private static void logic(EntityManager em) {
 Member member = new Member();
 em.persist(member);
 System.out.println("member.id = " + member.getId());
}
// 출력: member.id = 1

그 이유는 다음과 같습니다.

엔티티가 영속 상태가 되기 위해서는 식별자가 필요합니다. IDENTITY 전략의 경우, 식별자 생성을 데이터 베이스에 저장해야 구할 수 있으므로 em.persist()를 호출하여 객체를 영속성 상태로 만드는 순간 INSERT SQL이 호출됩니다. 따라서, 이 전략은 쓰기 지연이 동작하지 않습니다.

SEQUENCE

데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트입니다. SEQUENCE 전략은 이 시퀀스를 사용해서 기본 키를 생성합니다. 이 전략은 시퀀스를 지원하는 오라클, PostgreSQL, DB2, H2 데이터베이스에 사용됩니다

시퀀스 DDL을 설정합니다.

// 시퀀스 DDL
CREATE TABLE MEMBER (
	ID BIGINT NOT NULL PRIMARY KEY,
   DATA VARCHAR(255)
)

// 시퀀스 생성
CREATE SEQUENCE MEMBER_SEQ START WITH 1 INCREMENT BY 1;
// 시퀀스 매핑 코드
@Entity
@SequenceGenerator(
 name = "MEMBER_SEQ_GENERATOR",
 sequenceName = "MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
 initialValue = 1, allocationSize = 1)
public class Member{

 @Id
 @GeneratedValue(strategy = GenerationType.SEQUENCE,
                 generator = "MEMBER_SEQ_GENERATOR")
 private Long id;
 ...
 }

SEQUENCE 전략의 경우, IDENTITY 전략과 코드는 동일하지만, 내부 동작이 다릅니다. em.persist()를 호출 시, 데이터베이스 시퀀스를 사용해서 식별자를 조회합니다. 식별자를 엔티티에 할당한 후에 엔티티를 영속성 컨텍스트에 저장합니다. 이후 트랜잭션을 커밋해서 플러시가 일어났을 경우, 엔티티를 데이터베이스에 저장합니다.

IDENTITY와의 차이점은 IDENTITY의 경우, 영속성 컨텍스트에 저장하기 위해서 INSERT를 바로 날린다는 점입니다.

@SequenceGenerator 속성

  • name: 식별자 생성기 이름 (필수)
  • sequenceName: 데이터베이스에 등록되어 있는 시퀀스 이름 (기본값: hibernate_sequence)
  • initialValueDDL: 생성 시에만 사용됨. 시퀀스 DDL을 생성할 때 처음 시작하는 수를 지정 (기본값: 1)
  • allocationSize: 시퀀스 한 번 호출에 증가하는 수 (기본값: 50)
  • catalog, schema: 데이터 베이스 catalog, schema 이름

allocationSize가 기본값 50으로 존재하는 이유?

결론은 데이터베이스에 여러번 접근하는 것을 방지해서 성능적으로 장점이 있기 때문입니다.

SEQUENCE 전략은 데이터베이스 시퀀스를 통해 식별자를 조회하는 추가 작업이 필요합니다. 따라서 다음과 같이 데이터베이스와 2번의 통신을 합니다. 1. 식별자를 구하려고 데이터베이스 시퀀스를 조회한다.ex) SELECT MEMBER_SEQ.NEXTVAL … 2. 조회한 시퀀스를 기본 값으로 사용해 데이터베이스에 저장한다.ex) INSERT INTO MEMBER …

위와 같이 시퀀스에 접근하는 것을 줄이기 위해 JPA는 @SequenceGenerator.allocationSize를 사용합니다. 간단히 설명하면, 여기에 설정한 값만큼 한 번에 시퀀스 값을 증가시키고 나서 그만큼 메모리에 시퀀스 값을 할당합니다.예를들어, allocation 값이 50이면 시퀀스를 한 번에 50을 증가시킨 다음에 1 ~ 50까지는 메모리에 시퀀스 값을 할당합니다. 그리고 51이 되면 시퀀스 값을 100으로 증가시킨 다음 51 ~ 100까지 메모리에서 식별자를 할당합니다.

이 최적화 방법은 시퀀스 값을 선점하므로 여러 JVM이 동시에 동작해도 기본 키 값이 충돌하지 않는 장점이 있습니다. 반면에 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한 번에 많이 증가한다는 점을 염두해두어야 합니다. 이런 상황이 부담스럽고 INSERT 성능이 중요하지 않으면 allocationSize의 값을 1로 설정하면 됩니다.

Table

TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략입니다.

  • 장점: 모든 데이터베이스에 적용 가능
  • 단점: 성능이 떨어짐
// TABLE 전략  생성 DDL
create table MY_SEQUENCE(
	sequence_name varchar(255) not null ,
   next_val bigint,
   primary key ( sequence_name )
)

SEQUENCE 전략에서 @SequenceGenerator를 사용한 것처럼, Table은 @TableGenerator를 사용합니다.

// TABLE 전략 매핑 코드
@Entity
@TableGenerator(
 name = "BOARD_SEQ_GENERATOR",
 table = "MY_SEQUENCES",
 pkColumnValue = "BOARD_SEQ", allocationSize = 1)
public class Member {

 @Id
 @GeneratedValue(strategy = GenerationType.TABLE,
                 generator = "BOARD_SEQ_GENERATOR")
 private Long id;
 ...
 }

@TableGenerator 속성

  • name: 식별자 생성기 이름 (필수)
  • table: 키생성 테이블명 (기본값: hibernate_sequences)
  • pkColumnName: 시퀀스 컬럼명 (기본값: sequence_name)
  • valueColumnName: 시퀀스 값 컬럼명 (기본값: next_val)
  • pkColumnValue: 키로 사용할 값 이름 (기본값: 엔티티 이름)
  • initialValue: 초기 값, 마지막으로 생성된 값이 기준 (기본값: 0)
  • allocationSize: 시퀀스 한 번 호출에 증가하는 수 (기본값: 50)
  • catalog, schema: 데이터베이스 catalog, schema 이름
  • uniqueConstraints(DDL): 유니크 제약 조건을 지정할 수 있다

allocationSize가 기본값 50으로 존재하는 이유?

TABLE 전략은 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값으로 증가시키기 위해 UPDATE 쿼리를 사용합니다. 이 전략은 SEQUENCE 전략과 비교해서 데이터베이스와 한 번 더 통신하는 단점이 있습니다. 이 전략도 최적화하기 위해 allocationSize를 사용하면 되고, 방법은 SEQUENCE 전략과 같습니다.

댓글남기기