본문 바로가기
Programming/Database & Query

[JPA] GenerationType.Sequence 설정시 next value 두 번 호출하는 이유

by 읽고 쓰는 개발자 2022. 1. 25.

JPA Entity 객체의 Id 필드를 지정할 때, Generation type을 identity, sequence, table 등의 전략으로 설정할 수 있다. (관련 포스팅)

이 때 type을 sequence로 설정하고 persistence.xml 내 DDL auto 프로퍼티를 create로 주게 되면(꼭 해당 상황에서만 발생하지는 않지만)

sequence 생성 및 최초 실행 시 next 값을 두 번 호출한다.  

<property name="hibernate.hbm2ddl.auto" value="create" />
// 저장할 Member Entity
@Entity
@SequenceGenerator(
        name = "MEMBER_SEQ_GENERATOR")	// default 값
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
            generator = "MEMBER_SEQ_GENERATOR")
    private Long id;
}
// main 
public class JpaMain {
    public static void main(String[] args) {

       EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try {
            Member member = new Member();	// Entity 객체 생성
            member.setUsername("HelloA");   
            em.persist(member);        // 영속 - sequence 조회 후 자동 부여

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        }

sequence 값이 호출되는 시점은 em.persist(member) 실행 시점이다.

next value가 두 번 호출되는 것은 시퀀스 생성 시 increment 값과 관련이 있다.

hibernate는 성능 등의 이유로 increment default 값을 50으로 두고 있다.

시퀀스 값을 50씩 증가하게 두고, 그 사이의 값을 영속성 컨텍스트에서 사용하기 위함이다. 

이 때 H2 DB에서는 최초 call next value 호출에 1을 return한다.

DB sequence

최초에 1을 return 받으면 캐싱해놓고 사용할 범위가 없다. 

increment size는 일부 범위 시퀀스를 내부적으로 가지고 있으려는 용도인데, 1 즉, initial value를 return 받으면 캐싱할 범위가 없기 때문에 2번 호출하는 것이다.

두 번 호출하면 51 value가 리턴되고, 그렇게 되면 1 ~ 50 까지의 값을 하나의 App 내 영속성 컨텍스트에서 사용 가능하다.

 

최초 리턴( initial value)이 아니라서 next value(max)만 리턴받으면 초기값 세팅은 어떻게?

Application의 hibernate에서는 시퀀스 사이의 값들을 내부적으로 캐싱해놓고 사용하기를 원한다. 

1을 리턴 받았을 때는 한 번 더 호출하여 값을 세팅하지만, 최초값이 아닌 경우는 한 번만 호출하여 max값을 설정한다.

시작값은 return 받은 값(캐싱해놓고 사용하는 max값)에서 increment value를 뺀 후 계산하여 시작 값을 지정한다.

// 만약 return 받은 next value가 101이라면 대략 이런 계산으로 초기값 세팅
101 ( max 값 ) - 50 (increment value) = 51  

따라서 next value 한 번의 호출로 세팅이 가능하다. 이렇게 초기값을 51로 세팅해 놓은 후, max값 (100) 까지 자유롭게 사용할 수  있기 때문이다.

sequence 한 번 호출

hibernate-core 코드에서 확인하기

해당 내용은 hibernate-core에서 확인할 수 있다. 

아래는 em.persist() 메소드 호출한 후 동작하는 내부 로직 중 PooledOptimizer class 내의 sequence 세팅 로직 일부 소스코드이다. 

allocation size가 1이 아닐 때는 Optimizer 구현체 중 PooledOptimizer를 호출한다.

hibernate-core 내부 소스코드

allocationSize(Increment)가 1일 때는?

시퀀스 증가값을 1로 세팅하는 것은 캐싱하여 관리할 시퀀스 범위 지정 없이, 하나의 객체 생성할 때마다 sequence를 새로 호출하겠다는 의미이다.

따라서 매 객체 생성시마다 next value를 호출하기 때문에 최초 호출시에도 1번 호출하여 next value를 받고 해당 시퀀스 값을 사용하면 된다. 그 후 또 새로운 객체를 만들 때 next value로 두 번째 값을 call하여 사용하면 된다.  (캐싱할 범위 지정 필요하지 않기 때문)

allicationSize=1 일 때는 Optimizer 구현체 중 NoopOptimizer를 호출한다.

hibernate-core 코드에서 확인하기

 

OptimizerFactory에서 사이즈별로 구현체 반환하는 로직

optimizerfactory에서 increment size별로 optimizer 구현체를 구분하여 반환한다.