JPA
기본 키 생성 전략
dev-rootable
2024. 4. 24. 17:52
🚴♂️ 기본 키 할당 전략
JPA가 제공하는 기본 키 할당 방법은 직접 할당 방식과 자동 생성 방식이 있다.
직접 할당 방식은 애플리케이션에서 기본 키를 직접 할당하는 방식이고, 자동 생성 방식은 DB에 할당을 위임하거나 기본 키를 생성해 주는 별도의 수단을 통해 할당하는 것이다.
본 글에서는 직접 할당 방식과 RDBMS에서 많이 사용하는 자동 생성 방식에 대해 다뤄보고자 한다.
📌 직접 할당 방식
Entity를 생성할 때, Key Column에 @Id를 사용하면 된다.
@Getter
@Entity
@NoArgsConstructor
public class Member {
@Id
private Long id;
...
}
해당 전략은 EntityManager.persist()로 Entity를 1차 캐시에 저장하기 전에 애플리케이션에서 직접 기본 키를 할당해 주어야 한다.
Member member = Member.builder()
.id(1)
.name("kim")
.build();
📌 자동 생성 방식
자동 생성 방식은 @Id와 @GeneratedValue를 사용한다.
@GeneratedValue의 strategy 속성을 통해 4가지 전략을 선택할 수 있다.
이 방식은 직접 기본 키를 할당하면 안 된다.
🔸 AUTO
선택한 DB 방언에 따라 자동으로 IDENTITY, SEQUENCE, TABLE 전략을 자동으로 선택하는 전략
- DB를 변경해도 코드를 수정하지 않아도 되는 장점
- 키 생성 전략이 정해지지 않은 개발 초기 단계나 프로토타입 개발 시에 편리하게 사용 가능
- SEQUENCE나 TABLE이 선택될 경우, Sequence나 키 생성 Table을 미리 생성해 두어야 함
- 만약 DDL 자동 생성 기능을 사용한다면, Hibernate가 기본 값을 사용해 자동으로 생성해 준다.
🔸 IDENTITY
기본 키 생성을 DB에 위임하는 전략
- MySQL의 경우, AUTO_INCREMENT 기능을 통해 기본 키 제공
- 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
- 지연 쓰기 SQL 저장소를 사용하지 않는다.
🔍 AUTO_INCREMENT 동작
JPA는 영속성 컨텍스트라는 1차 캐시에 Entity를 저장(영속)시킨 후에 해당 트랜잭션이 Commit 될 때, 쓰기 지연 저장소에 모아둔 INSERT 쿼리들을 DB에 날린다. 이러한 동기화 작업을 Flush라 한다.
그런데, 1차 캐시에 Entity를 저장하려면 기본 키가 있어야 한다. 하지만 AUTO_INCREMENT는 DB에 기본 키 생성을 위임하기 때문에 INSERT 하기 전에는 기본 키가 없다. 그래서 해당 기능은 영속화하는 시점에 바로 DB에 INSERT 쿼리를 날린다. 결과적으로 쓰기 지연 SQL 저장소를 사용하지 않게 되는 것이다.
@Getter
@Entity
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
}
🔸 SEQUENCE
DB의 시퀀스 오브젝트를 사용하여 기본 키를 생성하는 전략
- DB 시퀀스는 유일한 값을 순서대로 생성하는 특별한 DB 오브젝트
- 주로 Oracle, PostgreSQL, DB2, H2에서 사용
- @SequenceGenerator를 통해 시퀀스 생성기를 등록할 수 있다.
- 쓰기 지연 SQL 저장소를 사용한다.
🔍 SEQUENCE 동작
Entity를 영속화(persist) 하기 전에 DB 시퀀스를 사용해서 식별자를 조회한다. 그 후에 조회한 식별자를 Entity에
할당한 후에 영속성 컨텍스트에 저장(영속화)한다. 이후 트랜잭션 Commit이나 Flush가 일어나면 실제 DB에 저장하게 된다.
@Getter
@Entity
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ",
initialValue = 1, allocationSize = 1
)
@NoArgsConstructor
public class Member extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ_GENERATOR")
@Column(name = "member_id")
private Long id;
}
✅ 사용 가능한 옵션과 성능 최적화
속성 | 설명 | 기본 값 |
name | 식별자 생성기 이름 | 없음 지정 필수 |
sequenceName | DB에 등록되어 있는 시퀀스 이름 | hibernate_sequence |
initialValue | DDL 생성시에만 사용, Sequence DDL 생성시 처음 시작 value를 설정 | 1 |
allocationSize | 시퀀스 한번 호출 시 증가하는 수(성능 최적화에 사용) | 50 |
catalog, schema | DB catalog, schema 이름 |
allocationSize를 통해 성능 최적화를 할 수 있다.
allocationSize에 설정한 값만큼 시퀀스를 가져올 수 있다. 그래서 필요한 만큼 미리 DB에 시퀀스를 올려놓고 식별자로 사용할 수 있어 JPA가 시퀀스에 접근하는 횟수를 줄일 수 있다. 예를 들어 기본 값인 50은 시퀀스를 한번 호출할 때 50개의 식별자로 사용할 값을 준다는 것이다. 해당 값은 서로 겹치지 않도록 DB에서 알아서 조정해 준다.
🔸 TABLE
키 생성 테이블을 사용하여 기본 키를 생성하는 전략
- SEQUENCE 전략과 동작 방식 동일
- @TableGenerator 필요
- 값을 조회하면서 Select 쿼리를, 그 후 값 증가를 위해 Update 쿼리를 사용
- SEQUENCE 전략과 비교하여 DB와 한번 더 통신하므로 성능상 단점
- SEQUENCE 전략처럼 allocationSize를 통해 최적화할 수 있다.
@Entity
@TableGenerator(
name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumValue = "MEMBER_SEQ", allocationSize = 1
)
@NoArgsConstructor
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "MEMBER_SEQ_GENERATOR")
@Column(name = "member_id")
private Long id;
}
✅ Options
속성 | 설명 | 기본 값 |
name | 식별자 생성기 이름 | 없음 지정 필수 |
table | 키 생성 테이블 명 | hibernate_sequences |
pkColumnName | 시퀀스 컬럼 명 | sequence_name |
valueColumnName | 시퀀스 값 컬럼 명 | next_val |
pkColumnValue | 키로 사용할 값 이름 | Entity 이름 |
initialValue | 초기 값, 마지막 생성된 값이 기준 | 0 |
allocationSize | 시퀀스 호출 시 증가 값(성능 최적화에 사용) | 50 |
catalog, schema | DB catalog, schema 이름 | |
uniqueConstraints(DDL) | 유니크 제약 조건을 지정 |
References:
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard