JPA에서는 기본 키를 자동으로 생성하는 다양한 전략을 제공합니다. 그중 대표적인 세 가지 자동 생성 전략은 IDENTITY, SEQUENCE, TABLE입니다. 각 전략은 데이터베이스의 동작 방식에 따라 다르게 동작하며, 적절한 자동 생성 전략을 선택하는 것이 중요합니다.
1. IDENTITY 전략
- IDENTITY 전략은 데이터베이스에 의존하여 기본 키를 자동으로 생성합니다.
- 주로 MySQL이나 SQL Server와 같은 데이터베이스에서 사용되며, 데이터베이스의 AUTO_INCREMENT 기능을 통해 기본 키가 생성됩니다.
- 즉, 엔티티를 영속성 컨텍스트에 추가할 때 바로 쿼리가 실행되고, 자동 증가된 값이 기본 키로 설정됩니다.
예시:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // IDENTITY 전략 사용
private Long id;
private String name;
}
- 장점: 설정이 간단하며, 데이터베이스가 자동으로 키를 생성.
- 단점: 엔티티를 영속성 컨텍스트에 추가할 때마다 쿼리가 즉시 실행되어야 하기 때문에, 다른 전략에 비해 성능이 떨어질 수 있음.
2. SEQUENCE 전략
- SEQUENCE 전략은 데이터베이스 시퀀스(Sequence) 객체를 사용하여 기본 키를 생성합니다.
- 주로 Oracle, PostgreSQL과 같은 데이터베이스에서 사용됩니다. 시퀀스는 데이터베이스에서 독립적인 객체로, 연속적인 숫자를 반환합니다.
@SequenceGenerator
어노테이션을 사용하여 시퀀스 이름과 기타 속성을 지정할 수 있습니다.
예시:
@Entity
@SequenceGenerator(name = "user_seq", sequenceName = "user_sequence", allocationSize = 1) // 시퀀스 생성 설정
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq") // SEQUENCE 전략 사용
private Long id;
private String name;
}
@SequenceGenerator
:name
은 제너레이터의 이름을 지정하고,sequenceName
은 데이터베이스에서 실제 사용하는 시퀀스 객체의 이름을 지정합니다.allocationSize
는 한 번에 할당할 시퀀스 값의 개수를 설정합니다.- 장점: 엔티티의 키를 미리 배치(batch)로 가져와 쿼리 성능을 높일 수 있음.
- 단점: 시퀀스를 지원하지 않는 데이터베이스에서는 사용할 수 없음.
3. TABLE 전략
- TABLE 전략은 기본 키를 생성하기 위해 별도의 키 생성 테이블을 사용합니다.
- 모든 데이터베이스에서 사용할 수 있으며, 별도의 테이블을 만들어 키 값을 관리합니다.
- TABLE 전략은 시퀀스를 지원하지 않는 데이터베이스에서도 사용할 수 있지만, 성능은 다른 전략에 비해 떨어질 수 있습니다.
예시:
@Entity
@TableGenerator(
name = "user_gen", // 테이블 제너레이터 이름
table = "key_generator", // 키를 생성하는 테이블 이름
pkColumnName = "gen_name", // 키 생성용 테이블에서 사용할 PK 컬럼
valueColumnName = "gen_value", // 키 값이 저장될 컬럼
pkColumnValue = "user_id", // 테이블에 저장될 키 식별자
allocationSize = 1 // 키 값을 증가시키는 단위
)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "user_gen") // TABLE 전략 사용
private Long id;
private String name;
}
@TableGenerator
: 키를 저장하고 관리하는 별도의 테이블을 설정할 수 있습니다.table
: 키를 저장할 테이블의 이름.pkColumnName
: 테이블에서 기본 키로 사용할 컬럼.valueColumnName
: 키 값을 저장할 컬럼.pkColumnValue
: 생성되는 키의 이름.
- 장점: 시퀀스 기능이 없는 데이터베이스에서도 사용할 수 있음.
- 단점: 성능이 상대적으로 느림. 별도의 테이블에 키 값을 저장하고 관리해야 하기 때문에, 테이블에 접근할 때 성능 저하가 발생할 수 있음.
자동 생성 전략 선택 기준
- IDENTITY: MySQL, SQL Server와 같은 AUTO_INCREMENT를 지원하는 데이터베이스에서 사용. 키를 자동으로 생성하고 바로 삽입하는 구조.
- SEQUENCE: Oracle, PostgreSQL과 같이 시퀀스를 지원하는 데이터베이스에서 사용. 키 생성이 데이터베이스의 시퀀스 객체에 의존.
- TABLE: 모든 데이터베이스에서 사용할 수 있지만, 성능이 중요하다면 권장되지 않음. 시퀀스 기능이 없는 데이터베이스에서 주로 사용.
정리
- IDENTITY: 기본 키를 데이터베이스에서 자동 생성(AUTO_INCREMENT)하는 방식. 즉시 INSERT 쿼리를 실행.
- SEQUENCE: 데이터베이스 시퀀스를 사용하여 기본 키를 생성. 미리 키를 가져올 수 있어서 성능이 좋음.
- TABLE: 별도의 테이블을 만들어 기본 키를 생성. 시퀀스가 없는 데이터베이스에서도 사용 가능하지만, 성능은 상대적으로 떨어질 수 있음.
각 전략은 데이터베이스 종류와 요구 사항에 맞춰 선택하는 것이 중요합니다.
**테이블 전략(TABLE 전략)**은 여러 테이블이 하나의 프라이머리 키를 공유하고, 이를 위해 별도의 테이블을 사용하여 기본 키 값을 관리하는 방식입니다. 이 전략은 시퀀스를 지원하지 않는 데이터베이스에서도 사용할 수 있으며, 테이블을 통해 기본 키 값을 관리하여 충돌을 방지합니다. 특히 동시성 문제를 처리할 때 FOR UPDATE같은 락을 사용하여 키 값을 안전하게 증가시키는 방법을 사용할 수 있습니다.
상황 설명:
- 여러 테이블이 공통된 프라이머리 키 시퀀스를 공유해야 할 때, 테이블 전략을 사용하면 각 테이블에서 고유한 ID를 생성하지 않고, 하나의 공통된 테이블을 통해 프라이머리 키 값을 증가시킬 수 있습니다.
- 이를 통해 ID 중복을 피하고 통합된 키 관리를 할 수 있습니다.
테이블 전략 설정
- 키를 관리하는 별도의 테이블 생성: 먼저, 데이터베이스에 키를 관리하는 테이블을 생성해야 합니다. 이 테이블은 각 테이블에 고유한 프라이머리 키 값을 제공하는 역할을 합니다.
예시 테이블 생성:
CREATE TABLE key_generator (
gen_name VARCHAR(50) NOT NULL, -- 키의 이름 (테이블 이름 등을 저장)
gen_value BIGINT NOT NULL, -- 현재 키 값
PRIMARY KEY (gen_name)
);
- gen_name: 어떤 테이블의 키를 관리하는지 식별할 수 있는 이름입니다.
- gen_value: 해당 테이블에서 사용할 마지막 프라이머리 키 값을 저장합니다. 새로운 프라이머리 키가 필요할 때 이 값을 증가시켜 사용합니다.
- JPA에서 테이블 전략 설정
JPA에서는 @TableGenerator 어노테이션을 사용하여 테이블 전략을 정의할 수 있습니다. 이 어노테이션은 기본 키를 관리할 테이블과 키 값을 관리하는 방법을 정의합니다.
예시 JPA 설정:
@Entity
@TableGenerator(
name = "shared_id_gen", // 제너레이터의 이름
table = "key_generator", // 키를 관리하는 테이블
pkColumnName = "gen_name", // 테이블의 PK 컬럼 이름
valueColumnName = "gen_value", // 키 값이 저장되는 컬럼
pkColumnValue = "shared_id", // 사용할 테이블(또는 엔티티)의 이름
allocationSize = 1 // 한 번에 증가시킬 키 값 (기본 1)
)
public class EntityA {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "shared_id_gen")
private Long id;
private String name;
}
@Entity
@TableGenerator(
name = "shared_id_gen", // 동일한 제너레이터 사용
table = "key_generator",
pkColumnName = "gen_name",
valueColumnName = "gen_value",
pkColumnValue = "shared_id", // 동일한 키를 공유하는 테이블
allocationSize = 1
)
public class EntityB {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "shared_id_gen")
private Long id;
private String description;
}
- name: shared_id_gen이라는 이름으로 키를 관리하는 제너레이터를 설정합니다. 이 이름은 다른 엔티티에서도 같은 키 제너레이터를 사용하도록 설정할 수 있습니다.
- table: key_generator라는 별도의 테이블에서 키 값을 관리합니다.
- pkColumnValue: 여러 테이블이 동일한 ID 시퀀스를 공유해야 하므로 pkColumnValue에 동일한 값을 지정합니다.
- allocationSize: 기본 키 값이 얼마나 증가할지 설정합니다. 1로 설정하면 매번 하나씩 증가합니다.
동시성 문제 해결 (FOR UPDATE)
여러 테이블이 동일한 프라이머리 키 값을 공유하고 키 값을 증가시키는 상황에서는 동시성 문제가 발생할 수 있습니다. 즉, 두 개 이상의 트랜잭션이 동시에 새로운 키 값을 가져오려고 하면 충돌이 발생할 수 있습니다. 이를 방지하기 위해 데이터베이스 락을 사용하여 동시성 문제를 해결할 수 있습니다.
FOR UPDATE를 사용한 락:
FOR UPDATE 구문은 데이터베이스에서 특정 행을 업데이트하거나 삭제하기 위해 락을 거는 구문입니다. 이 락은 다른 트랜잭션이 해당 데이터를 읽거나 수정하는 것을 방지합니다.
SQL 예시:
SELECT gen_value FROM key_generator WHERE gen_name = 'shared_id' FOR UPDATE;
위 쿼리는 gen_value에 대해 락을 걸어, 다른 트랜잭션이 동시에 접근하지 못하도록 합니다. 이후 새로운 키 값을 증가시키고, 그 값으로 기본 키를 할당할 수 있습니다.
JPA에서의 사용:
JPA에서는 기본적으로 FOR UPDATE를 직접 사용하는 것이 아니라, 트랜잭션 관리를 통해 동시성 문제를 해결할 수 있습니다. 하지만 Native Query를 사용해 FOR UPDATE와 같은 데이터베이스 락을 적용할 수도 있습니다.
Query query = entityManager.createNativeQuery(
"SELECT gen_value FROM key_generator WHERE gen_name = 'shared_id' FOR UPDATE");
정리
- **테이블 전략(Table Generation)**을 사용하면 여러 테이블이 하나의 공통된 프라이머리 키 시퀀스를 공유할 수 있습니다.
- 이를 위해 키 값을 관리하는 별도의 테이블을 설정하고, JPA에서 이를 기반으로 자동 키 생성을 관리합니다.
- 동시성 문제를 해결하기 위해 **FOR UPDATE**를 사용하여 키 값을 가져오는 동안 다른 트랜잭션이 동일한 키에 접근하지 못하도록 막을 수 있습니다.
이 전략을 사용하면 여러 테이블이 동일한 프라이머리 키 값을 공유하면서도 안전하게 키 값을 증가시킬 수 있는 방법을 제공합니다.
'JPA Study' 카테고리의 다른 글
서브쿼리 (Subquery) (1) | 2024.09.23 |
---|---|
이너 조인(Inner Join)과 레프트 조인(Left Join) (0) | 2024.09.20 |
JPA가 제공하는 주요 CRUD 기능 (0) | 2024.09.10 |
JDBC와 JPA의 관계 (0) | 2024.09.09 |
JPA(Java Persistence API) (1) | 2024.08.08 |