기본 키(Primary key)?
주 키 또는 프라이머리 키라고 하며, 관계형 데이터베이스에서 레코드의 식별자로 가장 적합한 것으로 선택, 정의된 후보 키입니다.(출처: 위키백과)
기본 키의 (제약) 조건
기본 키는 데이터베이 테이블의 레코드를 식별하기 때문에 따라오는 제약 조건이 있습니다.
- 기본 키의 값은 고유해야 하며(중복 X) 변하지 않아야 합니다.
- 두 개 이상의 열을 기본 키로 설정한 경우 한 열에 중복된 값이 있을 수는 있지만 기본 키에 정의된 모든 열의 값의 조합은 고유해야 합니다.
- 기본 키는 NULL값을 허용하지 않습니다.
- 테이블은 하나의 기본 키만 가질 수 있습니다.
기본 키 선택 전략
- 자연 키(natural key) : 주민등록번호, 이메일, 전화번호 등과 같이 비즈니스적인 의미가 있는 키입니다.
- 대리 키(surrogate key, 대체키) : 채번과 같이 비즈니스와 관련 없는 임의로 만들어진 키입니다.
자연 키보다는 대리 키를 기본 키로 사용합니다.
기본 키(Primary Key) 매핑 방법
- 직접 할당 :
user.setId(1L);
과 같이 기본 키를 직접 할당합니다. - 자동 생성 : 데이터베이스에서 자동으로 생성합니다. DB 벤더마다 지원하는 방식이 다르기 때문에 여러 가지 방법이 있습니다.
전략 설명 IDENTITY 기본 키 생성을 데이터베이스에 위임합니다. SEQUENCE 데이터베이스의 시퀀스를 사용합니다. TABLE 기본 키 전용 테이블을 만들고 시퀀스처럼 사용합니다. 테이블을 활용하기 때문에 모든 데이터베이스에서 사용 가능합니다. AUTO 데이터베이스 방언(Database disalect)에 맞게 자동으로 설정됩니다.(Default)
1. 직접 할당
@javax. persistence.Id만
사용합니다.
JPA 표준에는 기본 키 값 없이 저장하면 어떤 예외가 발생하는지에 대한 정의가 없습니다. hibernate
사용 시 org.hibernate.id.IdentifierGenerationException
예외가 발생합니다.
Jakarta Persistence에 따르면 직접 할당은 아래의 타입으로만 가능하다고 합니다.
- primitive type
java.lang.String
- enum
- Java serializable types
- primitive wapper type
java.util.Date
java.sql.Date
java.time.LocalDateTime
java.math.BigDecimal
java.math.BigInteger
- 사용자가 직접 정의한
Serializable interface
를 구현한 타입
하지만 Hibernate
는 UUID와 같은 다른 유형의 타입도 가능하다고 합니다.(하이버네이트 공식문서)
2. 자동생성
@javax.persistence.Id
와 @javax. persistence.GeneratedValue를
같이 사용합니다.
hibernate.id.new_generator_mapping = true 속성을 추가해야 한다.
(과거 버전과의 호환성 때문에 기본 값이 false)
1. IDENTITY
@GeneratedValue(strategy = GenerationType.IDENTITY)
- 주로 MySQL, PostgreSQL, SQL Server, DB2 등에서 사용합니다.
Identity 전략의 최적화
DB에 값을 저장하고 나서야 PK값을 알 수 있기 때문에 기본 키를 할당하려면 DB를 먼저 조회해야 합니다.
Entity가 영속 상태가 되려면 식별자가 반드시 필요한데, IDENTITY 전략은 Entity를 DB에 저장해야 식별자를 구할 수 있으므로 모순이 생깁니다.
따라서 em.persist()
를 호출하는 즉시 DB에 INSERT 됩니다. 그렇기 때문에 트랜잭션에서 지원하는 쓰기 지연이 동작하지 않습니다.JDBC3
에 추가된 Statement.getGeneratedKeys()
를 통해 데이터를 저장함과 동시에 생성된 PK값도 얻을 수 있습니다. Hibernate
는 이 메서드를 이용해 DB와 한 번만 통신합니다.
2. SEQUENCE
@GeneratedValue(strategy = GenerationType.SEQUENCEW)
- Oracle, PostgreSQL, DB2, H2에서 사용합니다.
@SequenceGenerator
를 통해 시퀀스의 정보를 입력합니다.@SequenceGenerator
는 클래스 레벨, 필드 레벨 모두 사용 가능합니다.
@SequenceGenerator(
name = "BOARD_SEQ_GENERATOR",
sequenceName = "BOARD_SEQ",
initialValue = 1,
allocationSize= 3)
@Entity
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "BOARD_SEQ_GENERATOR")
private Long id;
}
-- CREATE SEQUENCE [sequenceName] START WITH [initialValue] INCREMENT BY [allocationSize];
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 3;
Sequence 전략의 최적화
em.persist()
를 호출하면 DB 시퀀스를 사용하여 식별자를 조회, 조회한 식별자를 할당한 후 영속성 콘텍스트에 저장합니다.
시퀀스를 조회하는 추가 작업이 필요하기 때문에 DB와 2번 통신한다. 이 시퀀스에 접근하는 횟수를 줄이기 위해 allocationSize
속성을 사용합니다.
시퀀스를 조회할 때 한 번에 allocationSize만큼 증가시킨 후 메모리에서 식별자를 할당합니다. 이를, Pooled optimizer
라고 합니다.
무작정 큰 숫자를 설정한 경우 애플리케이션이 중간에 종료되었을 때 시퀀스 공백이 생기기 때문에 적당한 값인 50을 사용합니다.
@SequenceGenerator
속성 | 설명 | 기본 값 |
---|---|---|
name | 식별자 생성기 이름(필수) | 필수 |
sequenceName | DB에 등록되어있는 시퀀스 이름 | hibernate_sequence |
initialValue | 시퀀스 DDL을 생성할 때 처음 시작하는 수 지정 | 1 |
allocationSize | 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용) | 50 |
catalog, schema | DB catalog, schema 이름 |
3. TABLE
- 우선, Table 전략은 사용을 권장하지 않습니다.
@GeneratedValue(strategy = GenerationType.TALBE
- 시퀀스 대신 테이블을 사용한다는 것만 제외하면 시퀀스 전략과 내부 동작 방식이 같습니다.
- 시퀀스 테이블에 값이 없으면 JPA가 값을 INSERT 하면서 초기화하므로 값을 미리 넣어둘 필요가 없습니다.
@TableGenerator
를 통해 Table 정보 입력합니다.@TableGenerator
는 클래스 레벨, 필드 레벨 모두 사용 가능합니다.
@TableGenerator(
name = "BOARD_SEQ_GENERATOR",
table = "MY_SEQ",
pkColumnValue = "BOARD_SEQ",
initialValue = 0, // Sequence 전략과는 다르게 기본 값은 0 입니다. 0으로 설정되어야 실제 DB에 저장될때 1부터 저장됩니다.
allocationSize = 3)
@Entity
public class Board {
@Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "BOARD_SEQ_GENERATOR")
private Long id;
}
Table 전략의 최적화
Sequence 전략과 동일하게 allocationSize
를 통해 최적화합니다.
@TableGenerator
속성 | 설명 | 기본 값 |
---|---|---|
name | 식별자 생성기 이름(필수) | 필수! |
table | 키 생성 테이블명 | hibernate_sequences |
pkColumnName | 시퀀스 컬럼명 | sequence_name |
valueColumnName | 시퀀스 값 컬럼명 | next_val |
pkColumnValue | 키로 사용할 값 이름 | Entity 이름 |
initailValue | 초기 값, 마지막으로 생성된 값이 기준 | 0 |
allocationSzie | 시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용) | 50 |
catalog, schema | DB catalog, schema 이름 | |
uniqueConstraints(DDL) | 유티크 제약 조건 지정 |
4. AUTO
@GeneratedValue(strategy = GenerationType.AUTO
- @GeneratedValue.strategy의 기본 값이므로 @GeneratedValue만 써도 됩니다.
- DB에 따라
IDENTITY, SEQUENCE, TABLE
전략 중 하나를 자동으로 선택합니다. - DB를 변경해도 코드를 수정할 필요가 없습니다.
- SEQUENCE나 TABLE 전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어두어야 하는데
스키마 자동 생성 기능을
사용한다면 하이버네이트가 기본값을 사용해서 자동으로 만들어 줍니다.