엔티티에 식별자를 할당하는 데에는 4가지 방법이 있는데, 그 중에 SEQUENCE 전략이 있다.
@GeneratedValue(strategy = GenerationType.IDENTITY) : DB에 위임하면 알아서 auto_increment해줌
@GeneratedValue(strategy = GenerationType.SEQUENCE) : DB sequence object 사용
@GeneratedValue(strategy = GenerationType.TABLE) : 키생성 전용 테이블 만들기
@GeneratedValue(strategy = GenerationType.AUTO) : 위의 세가지 전략중 적절하게 지정
SEQUENCE전략 사용 설정
@SequenceGenerator 에노테이션은 시퀀스 생성기를 생성하는 애노테이션이며, DB에서 생성한 시퀀스를 매핑하고 생성하는 역할을 한다.
만든 생성기를 사용하기 위해서는 @GeneratedValue 애노테이션을 이용해 속성을 지정해주고, 설정하면 사용할 수 있다.
시퀀스전략 엔티티를 DB에 저장
시퀀스 전략을 사용하기 위해서는 직접 id값을 할당하면 안된다. 엔티티를 생성하고 persist를 실행하면 DB의 시퀀스를 호출하여 값을 얻고, 이후 얻어온 값을 id 식별자에 할당한다. 이후 얻어온 값을 user의 id식별자에 할당하고, 다시 영속성 컨텍스트의 1차 캐시에도 저장해준다.
package hellojpa;
import javax.persistence.*;
@Entity
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ",
initialValue = 1, allocationSize = 50)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR") //생성한 시퀀스제너레이터 사용
private Long id;
@Column(name = "name", nullable = false)
private String username;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Member() {
}
}
에러 발생
그런데 나의 경우에 allocationsize를 1이 아닌 값으로 바꾸어주면 에러가 발생했다.
심지어 SequenceGenerator.class에 가보면 allocationsize의 기본값이 50으로 정의되어 있는데, 기본값으로 놓고 실행해도 에러가 뜬다. 간단하게 에러 내용을 살펴보면, 'The increment size of the [MEMBER_SEQ] sequence is set to [50] in the entity mapping while the associated database sequence increment size is [1]' 이라고 나와있다. 한마디로 DB의 allocationsize가 1인데 내가 50으로 셋팅해놔서 문제가 생겼다는 말이다. 그래서 DB의 이미 존재하는 [MEMBER_SEQ] 시퀀스를 drop하고 새로 작동해보았더니 잘 돌아갔다. 아무래도 persistence.xml의 hibernate.hbm2ddl.auto옵션을 create로 한다고 해도 시퀀스는 초기화가 되지 않는 것 같다. 내 생각에는 같이 초기화되어야 맞는거같긴한데.. 여튼 찝찝하지만 해결은 했다.
.
.
.
라고 종결짓기엔 너무 궁금해서 다시 allocationsize = 1로 변경해서 다시 실행해보았는데 이번에는 db의 시퀀스가 drop(초기화, 갱신)됐다! 50에서 1로 변환하는 건 가능한건가보다.
다시 1에서 50으로 변환해보았더니 처음에 발생했던 오류가 또 발생했다. 그 후에도 여러가지 시도를 해보았는데 정리하면
allocationsize를
1. 1이 아닌수에서 1로 변환할땐 오류가 발생하지 않는다.
2. 1에서 50으로 변환하면 오류가 발생한다.
3. 50에서 50보다 큰 값으로 변환하면 오류가 발생한다.
4. 50에서 50보다 작은 값으로 변환하면 오류가 발생한다.
1이 아닌 수에서 1로 변환할때만 오류가 발생하지 않는다. 대체 왜 그런건지는 구글링해도 못찾았고, 대신 allocationSize의 작동 과정에 대해서 알게 되었다.
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)" 공식을 사용하여 시작값을 정합니다.

예시 1. DB의 시퀀스 증가값이 50, allocationSize 가 기본값인 50인 경우
allocationSize - 50CREATE SEQUENCE USER_SEQ START WITH 1 INCREMENT BY 50; c
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에 쿼리를 실행하지 않고 직접 가상의 시퀀스 값을 엔티티에 할당할 것입니다.
예시 2. DB의 시퀀스 증가값이 1, allocationSize 가 기본값인 50인 경우 : 내가 문제가 발생한 경우
다음의 경우 식별자 관련 예외가 발생하므로 시퀀스 증가값이 1인 경우 무조건 allocationSize를 1로 할당하여 사용하도록 합니다.
allocationSize - 50CREATE 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()를 실행하면 엔티티 식별자 중복 에러가 발생합니다.
예시 3. DB의 시퀀스 증가값이 1, allocationSize 가 기본값인 1인 경우
이 경우는 매우 단순한 경우로, 당연하게도 persist()를 실행할 때마다 DB의 시퀀스로부터 값을 얻어 식별자로 할당합니다. 성능이슈가 없다면 매우 간단 명료하므로 사용해도 좋습니다. 상황에 맞게 allocationSize 를 조절하여 사용합니다.
출처 : https://dololak.tistory.com/479
정리하자면 allocationSize값에 따라서 범위 내에서는 DB에 시퀀스를 호출하지 않고, 시작값과 종료값을 정하여 그 범위 내에서는 메모리에서 관리하다가 메모리가 꽉 차면 다시 공식에 따라서 DB에 시퀀스를 호출한다는 것이다. 이때 DB의 시퀀스 증가값과 allocationSize가 다르면 범위를 잡는 공식 실행시 오류가 생겨 두개가 다르면 안된다는 내용이다.
나는 allocationsize를 1이 아닌수에서 1로 변환할때만 오류가 발생하지 않았다(1번 case). 생각해보면 allocationSize가 1이 아닌수에서 1로 변환한다는 얘기는, 50에서 1로 변환했다고 하면, DB의 시퀀스 증가값이 50이고, allocationSize가 1인 경우일 것이다. 이 경우는 실행시간의 이득은 없겠지만, 일단 에러가 발생하지 않는다는 것을 알 수 있다.
(create옵션을 두어도 시퀀스는 drop된 후 실행되지 않는다고 가정했을때. 상식적으로 그 케이스만 drop된다는것은 말도 안된다고 생각하고 구글링해도 나오지 않아서,,)
하지만 왜 "persistence.xml의 hibernate.hbm2ddl.auto옵션을 create로 한다고 해도 시퀀스는 초기화가 되지 않는 지"에 대한 해답은 찾지 못했다. 아무래도 h2 DB가 최신버전들이 제대로 동작하지않아 2019년에 나온 버전을 써야 그나마 버그가 덜 발생한다고 들었는데, 이것도 h2 DB의 버그중에 하나가 아닐까..
'[ Backend ] > Spring DB, JPA' 카테고리의 다른 글
[JPA] 연관관계 매핑(2) - 다양한 연관관계 (0) | 2022.01.06 |
---|---|
[JPA] 연관관계 매핑(1) - 객체와 테이블 (0) | 2022.01.04 |
[JPA] 엔티티 매핑 (0) | 2021.12.31 |
[JPA] JPA 구조, 특성 (0) | 2021.12.29 |
[JPA] JPQL 예시 (0) | 2021.12.28 |