JPA 식별자
JPA는 엔티티들을 논리적인 공간인 영속성 컨텍스트에서 관리하는데, 엔티티를 구분할 수 있는 식별자가 필요합니다. 식별자가 되는 필드는 엔티티 클래스의 @Id 애노테이션을 통해 지정할 수 있습니다.

엔티티가 영속성 컨텍스트에 들어가 JPA에 관리되는 시점에는 반드시 식별자로 지정된 필드에 식별자 값이 할당되어 있어야 합니다.


SEQUENCE(시퀀스) 전략
JPA에서 엔티티에 식별자를 할당하는 방법은 여러가지가 있지만 그 중 Oracle, DB2, H2, PostgreSQL 등에서 사용할 수 있는 시퀀스 기능을 활용한 SEQUENCE 전략이 있습니다. SEQUENCE 전략은 DB에서 시퀀스 기능을 지원해야만 사용가능한 전략이므로 DBMS에 종속적인 식별자 할당 전략입니다.


다음과 같이 user 테이블이 있고 user 테이블에서 사용할 USER_SEQ 시퀀스가 있습니다.
CREATE TABLE user (
    ID BIGINT  NOT NULL PRIMARY KEY ,
    NAME VARCHAR(255)
);
 
 
CREATE SEQUENCE USER_SEQ START WITH 1 INCREMENT BY 1;                                            
cs



생성한 user 테이블과 시퀀스를 바탕으로 User 엔티티 클래스에서는 다음과 같이 SEQUENCE 전략을 설정할 수 있습니다.
@Entity
@SequenceGenerator(
            name="USER_SEQ_GEN"//시퀀스 제너레이터 이름
            sequenceName="USER_SEQ"//시퀀스 이름
            initialValue=1//시작값
            allocationSize=1 //메모리를 통해 할당할 범위 사이즈
            )
public class User {
 
 
    @Id
    @GeneratedValue(
            strategy=GenerationType.SEQUENCE, //사용할 전략을 시퀀스로  선택
            generator="USER_SEQ_GEN" //식별자 생성기를 설정해놓은  USER_SEQ_GEN으로 설정        
            )
    private Long id;
 
 
    private String name;
 
 
    public Long getId() {
        return id;
    }
 
 
    public String getName() {
        return name;
    }
 
 
    public void setName(String name) {
        this.name = name;
    }
}
cs




시퀀스 생성기 설정
가장 눈에 띄는 부분은 @SequenceGenerator 애노테이션입니다. @SequenceGenerator는 시퀀스 생성기를 설정하는 애노테이션입니다. DB에서 생성한 시퀀스를 맵핑하고 맵핑된 시퀀스를 통해 시퀀스를 생성하는 역할을 합니다. 속성들에 대해서는 이후에 자세히 설명합니다.
@SequenceGenerator(
            name="USER_SEQ_GEN"//시퀀스 제너레이터 이름
            sequenceName="USER_SEQ"//시퀀스 이름
            initialValue=1//시작값
            allocationSize=1 //메모리를 통해 할당할 범위 사이즈                                
            )
cs




식별자 할당 전략 및 시퀀스 생성기 사용 설정
DB에서 생성한 시퀀스를 이용하여 식별자를 생성하는 시퀀스 생성기를 생성한 이후에는 해당 시퀀스 생성기를 사용하겠다는 설정을 해주어야 합니다. @GeneratedValue 애노테이션을 통해 strategy 속성을 GenerationType.SEQUENCE로 하여 식별자를 할당하는 전략을 시퀀스 방식으로 하겠다는 설정과, 식별자를 생성해줄 식별자 생성기를 USER_SEQ_GEN로 사용하겠다는 설정을 generator 속성을 통해 설정합니다.
    @GeneratedValue(
            strategy=GenerationType.SEQUENCE, //사용할 전략을 시퀀스로  선택
            generator="USER_SEQ_GEN" //식별자 생성기를 설정해놓은  USER_SEQ_GEN으로 설정
            )
    private Long id;
cs




시퀀스 전략 엔티티를 DB에 저장
시퀀스 전략을 사용하는 User 엔티티를 DB에 저장하는 과정입니다. 먼저 User 엔티티를 생성할 때 시퀀스를 사용할 것이므로 id값을 직접 할당하지 않습니다. 직접 id를 할당하고 persist() 하게 되면 예외가 발생합니다. 

엔티티를 생성하고 persist()를 실행하면 DB의 USER_SEQ 시퀀스를 호출하여 값을 얻습니다. 이후 얻어온 값을 User의 id 식별자에 할당하고, 영속성 컨텍스트에 저장합니다. 이후 트랜잭션이 commit() 되는 시점에 엔티티를 DB에 flush하여 insert 쿼리를 실행합니다.
        try {
            transaction.begin();
            
            User user = new User();
            user.setName("kim");
 
 
            entityManager.persist(user); //select USER_SEQ.nextval from  dual 쿼리 실행하여 id 값을 엔티티에 할당
            transaction.commit(); //insert into User (name, id) values  (?, ?) 쿼리 실행
        } catch (Exception e) {
            e.printStackTrace();
            transaction.rollback();
        } finally {
            entityManager.close();
        }
cs

 



@SequenceGenerator 애노테이션
DB에 생성한 시퀀스를 바탕으로 식별자를 생성하는 시퀀스 생성기를 설정하는 애노테이션입니다. 클래스 단위 또는 식별자 필드에서 @GeneratedValue 애노테이션 설정과 함께 사용 가능합니다.
속성
설명
기본값
name
@GeneratedValue에서 지정할 수 있는 시퀀스 생성기 이름을 등록합니다.
필수값
sequenceName
데이터베이스에 생성해둔 시퀀스 이름
hibernate_sequence
initialValue
DDL 생성 하는경우 사용하며, 시퀀스를 생성할 DDL에서 처음 시작하는 값을 지정합니다.
1
allocationSize
JPA에서 가상으로 관리할 시퀀스 할당 범위로 성능 최적화를 위해 값을 수정할 수 있습니다.
기본값은 50이며, 1로 설정하는 경우 매번 insert시마다 DB의 시퀀스를 호출합니다.
50
catalog, schema
DB의 카타로그와 스키마 이름




allocationSize 속성 설명 및 사용시 주의사항
allocationSize 값은 DB에 매번 시퀀스를 호출하지 않기 위해서 최적화를 위해 존재하는 속성입니다. 하이버네이트의 경우 기본값은 50인데, 값을 바꾸지 않는다면 최초에 DB에 시퀀스를 호출한 이후 50까지는 메모리에서 현재 시퀀스 값을 저장한 이후 가상으로 증가시키며 관리하고 51이 되는 시점에 DB의 시퀀스를 한번 더 호출한 이후 그 값으로부터 50만큼인 100까지 가상으로 시퀀스 식별자를 관리하게 됩니다.

주의할점이 있다면 DB의 시퀀스 증가값이 1인 경우입니다. DB의 시퀀스 증가값이 1인 상태는 반드시 allocationSize 또한 1로 맞춰 주어야 합니다. 이유는 allocationSize 가 다음과 같은 알고리즘으로 동작하기 때문입니다.


1. 최초 persist() 실행시에 설정에 따른 DB 시퀀스를 두 번 호출하여 첫번째 시퀀스값을 가상으로 관리할 시작값, 두번째 시퀀스 값을 가상으로 관리할 범위의 끝(MAX)값으로 지정합니다.
2. 이후에는 persist()를 실행해도 db에 시퀀스를 호출하지 않고 메모리에서 가상으로 관리하며 할당합니다. persist() 실행시마다 메모리에서 관리하는 가상의 값을 1씩 증가시키며 엔티티에 할당합니다.
3. 어느 시점에 다다르면 엔티티에 식별자를 할당할 값이 관리할 범위의 끝(MAX)이 되고, 이후 다시 한번 persist()를 실행하는 시점에 DB에 시퀀스를 호출합니다.
4. 다시 호출한 시퀀스값을 가상으로 관리할 끝(MAX)값으로 바꾸고 시작값 또한 변경하는데 끝(MAX)값 - (allocationSize - 1) 공식을 사용하 시작값을 정합니다.



DB의 시퀀스 증가값이 50, allocationSize 가 기본값인 50인 경우
allocationSize - 50
CREATE SEQUENCE USER_SEQ START WITH 1 INCREMENT BY 50;                                        
cs

1. 최초에 persist()시 엔티티의 식별자를 구하기 위해 DB의 시퀀스를 두 번 호출합니다.
select USER_SEQ.nextval from dual 두 번 호출. 1과 51이 각각 리턴되는데, 1을 JPA가 메모리에서 관리할 시작값, 51을 끝(MAX)값 으로 지정합니다. 엔티티에 51의 식별자가 할당되는 때까지는 DB에 시퀀스를 호출하지 않고 JPA가 직접 가상의 시퀀그 값을 할당할 것입니다.

2. persist()로 엔티티를 계속 저장하다 보니 어느새 가장 최근에 persist() 실행시 할당했던 값이 가상의 시퀀스 끝값인 51입니다.

3. 다시 한번 persist()를 통해 엔티티를 저장하려 하는 시점에 DB에 시퀀스를 호출합니다. 101이 리턴되고 이를 다시 한번 JPA가 가상으로 관리할 시퀀스 MAX값으로 지정합니다.

4. 가상으로 관리할 시작값을 정해야 하는데 가장 최근에 사용한 51 + 1 을 하면 간단할 것 같지만 그렇지 않습니다. 공식을 적용하여 101 - (50 - 1) = 52를 시작값으로 지정합니다. 그리고 이 52를 현재 persist() 하려는 엔티티에 할당하고 커밋합니다.

5. 이후에는 2, 3, 4 번 과정을 반복하며, persist() 실행시마다 다음 MAX값이 될 때가지 DB에 쿼리를 실행하지 않고 직접 가상의 시퀀스 값을 엔티티에 할당할 것입니다.








DB의 시퀀스 증가값이 1, allocationSize 가 기본값인 50인 경우
다음의 경우 식별자 관련 예외가 발생하므로 시퀀스 증가값이 1인 경우 무조건 allocationSize를 1로 할당하여 사용하도록 합니다.
allocationSize - 50
CREATE SEQUENCE USER_SEQ START WITH 1 INCREMENT BY 1;                                        
cs

1. 최초에 persist()시 엔티티의 식별자를 구하기 위해 DB의 시퀀스를 두 번 호출합니다.
select USER_SEQ.nextval from dual 두 번 호출. 1과 2가 각각 리턴되는데, 1을 JPA가 메모리에서 관리할 시작값, 2를 끝(MAX)값 으로 지정합니다. 엔티티에 2의 식별자가 할당되는 때까지는 DB에 시퀀스를 호출하지 않고 JPA가 직접 가상의 시퀀그 값을 할당할 것입니다.

2. persist()로 엔티티를 계속 저장하다 보니 어느새 가장 최근에 persist() 실행시 할당했던 값이 가상의 시퀀스 끝값인 2 입니다.

3. 다시 한번 persist()를 통해 엔티티를 저장하려 하는 시점에 DB에 시퀀스를 호출합니다. 3이 리턴되고 이를 다시 한번 JPA가 가상으로 관리할 시퀀스 MAX값으로 지정합니다.

4. 가상으로 관리할 시작값을 정해야 하는데 가장 최근에 사용한 2 + 1 을 하면 간단할 것 같지만 그렇지 않습니다. 공식을 적용하여 3 - (50 - 1) = -46을 시작값으로 지정합니다. 그리고 이 -46을 현재 persist() 하려는 엔티티에 할당하고 커밋합니다.

5. 이후에는 2, 3, 4 번 과정을 반복하며, persist() 실행시마다 다음 MAX값이 될 때가지 DB에 쿼리를 실행하지 않고 직접 가상의 시퀀스 값을 엔티티에 할당할 것입니다. 무언가 이상합니다 식별자에 음수값인 -46이 할당되고 이후 -45, -44등의 식별자가 할당되어 persist()됩니다. 어느순간 가상 관리하는 할당 식별자가 1이 되는 순간 persist()를 실행하면 엔티티 식별자 중복 에러가 발생합니다.






DB의 시퀀스 증가값이 1, allocationSize 가 기본값인 1인 경우
이 경우는 매우 단순한 경우로, 당연하게도 persist()를 실행할 때마다 DB의 시퀀스로부터 값을 얻어 식별자로 할당합니다. 성능이슈가 없다면 매우 간단 명료하므로 사용해도 좋습니다. 상황에 맞게 allocationSize 를 조절하여 사용합니다.
블로그 이미지

도로락

IT, 프로그래밍, 컴퓨터 활용 정보 등을 위한 블로그

댓글을 달아 주세요! 질문 환영합니다!

  • 코린이 2021.11.21 13:34  댓글주소  수정/삭제  댓글쓰기

    유튜브 튜토리얼 보면서 스프링부트로 mysql 연동한 api 설계중인데 SequenceGenerator 나 GeneratedValue 어노테이션이 너무 이해가 안가던 찰나에 이런 좋은 글을 봤네요... 각각이 어떤걸 의미하는지 자세한 설명이 필요했었는데 덕분에 잘 이해하고 갑니다 :)