JPA가 제공하는 기본 키 생성 전략
- 직접 할당 전략 : 기본 키를 애플리케이션에서 직접 할당합니다.
- 자동 생성 전략 : 대리 키를 사용하는 방식으로 IDENTITY, SEQUENCE, TABLE 전략이 있습니다.
- IDENTITY : 기본 키 생성을 데이터베이스에 위임하는 방식입니다.
- SEQUENCE : 데이터베이스의 시퀀스를 사용해서 기본 키를 할당하는 방식입니다.
- TABLE : 키 생성 테이블을 이용하는 방식입니다.
자동 생성 전략을 사용하려면 persistence.xml에 hibernate.id.new_generator_mappings = true 속성을 추가해야 합니다. 이는 하이버네이트에서 JPA 규격에 맞으며 더 효과적으로 개발한 새로운 키 생성 전략이며 과거 버전과의 호환성유지를 위해 기본값이 false인 속성입니다. 이 옵션을 설정하면 키 생성 성능을 최적화하는 allocationSize 속성을 사용하는 방식이 달라진다고 합니다.
기본 키 직접 할당 전략
- 기본 키를 직접 할당하기 위해서는 @Id만 사용하여 매핑해줍니다.
- @Id 적용 가능한 자바 타입은 다음과 같습니다.
- 자바 기본형
- 자바 래퍼( Wrapper )형
- String
- java.util.Date
- java.math.BigDecimal
- java.math.BigInteger
- em.persist( ) 로 엔티티를 저장하기 전에 애플리케이션에서 기본 키를 직접 할당하는 방법입니다.
Member member = new Member();
member.setId("member1"); // 기본 키 직접 할당
em.persist(member);
IDENETITY 전략
- IDENTITY는 기본 키 생성을 데이터베이스에 위임하는 전략입니다.
- 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용합니다.
- @GeneratedValue 어노테이션을 사용하여 식별자 생성 전략을 IDENTITY로 선택합니다.
- 데이터베이스에 값을 저장할 때 ID 컬럼을 비워두면 데이터베이스가 식별자 값을 생성하여 채워줍니다.
- 이 전략을 사용하는 경우, JPA는 기본 키 값을 얻어오기 위하여 데이터베이스를 추가로 조회합니다.
@Entity
public class Member{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // IDENTITY 속성을 사용
private Long id;
...
}
- IDENTITY 전략을 사용하는 경우 데이터를 데이터베이스에 INSERT한 후에 기본키 값을 조회할 수 있습니다. 따라서, 엔티티의 식별자 값을 할당하려면 DB를 추가로 조회해야 합니다.
- JDBC3에 추가된 Statement.getGeneratedKeys( )를 사용하여 데이터베이스 저장과 동시에 기본 키 값을 얻어 올 수 있습니다. 하이버네이트는 이 메서드를 이를 사용하여 데이터베이스에 한 번만 통신합니다.
- 엔티티가 영속상태가 되려면 식별자가 반드시 필요합니다. IDENTITY 전략을 사용하는 경우 식별자 값을 매핑하기 위해 em.persist( ) 를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달됩니다. 따라서 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않습니다.
SEQUENCE 전략
- 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 데이터베이스 오브젝트입니다.
- SEQUENCE 전략은 시퀀스를 사용하여 기본 키를 생성하는 전략입니다.
- 시퀀스를 지원하는 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용할 수 있습니다.
SEQUENCE 전략을 사용하는 방법
1. 데이터베이스에 시퀀스 생성
CREATE TABLE BOARD{
ID BIGINT NOT NULL PRIMARY KEY,
DATA VARCHAR( 255 )
}
// 시퀀스 생성하기
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1; // 1부터 1씩 증가하며 할당합니다.
2. 데이터베이스의 시퀀스와 엔티티 매핑
@Entity
@SequenceGenerator(
name = "BOARD_SEQ_GENERATOR", // 시퀀스 생성기 이름
sequenceName = "BOARD_SEQ", // 매핑할 데이터베이스 시퀀스 이름
initialValue = 1, allocationSize = 1)
public class Board{
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "BOARD_SEQ_GENERATOR")
private Long id;
...
}
- @SequenceGenerator을 사용하여 BOARD_SEQ_GENERATOR 시퀀스 생성기를 등록하였습니다.
- sequenceName 속성을 사용하여 실제 데이터베이스의 시퀀스인 BOARD_SEQ 시퀀스와 매핑합니다.
- 키 생성 전략을 GenerationType.SEQUENCE로 설정하고 generator 속성으로 방금 생성한 시퀀스 생성기를 선택합니다.
SEQUENCE 전략은 em.persist( ) 를 호출할 때 먼저 데이터베이스 시퀀스를 이용하여 식별자를 조회합니다.
그리고 조회한 식별자를 엔티티에 할당한 후 엔티티를 영속성 컨텍스트에 저장합니다.
이후 트랜잭션을 커밋해서 플러시가 일어나면 엔티티를 데이터베이스에 저장합니다.
이와 달리 IDENTITY 전략은 엔티티를 데이터베이스에 저장한 후 식별자를 조회해서 엔티티에 식별자를 할당합니다.
@SequenceGenerator
속성 | 기능 | 기본값 |
name | 식별자 생성기 이름 | 필수 |
sequenceName | 데이터베이스에 등록되어 있는 시퀀스 이름 | hibernate_sequence |
initialValue | DDL 생성 시에만 사용됨. 시퀀스 DDL을 생성할 때 처음 시작하는 수를 지정한다. | 1 |
allocationSize | 시퀀스 한 번 호출에 증가하는 수 ( 성능 최적화에 사용됨 ) |
50 |
catalog, schema | 데이터베이스 catalog, schema 이름 |
SEQUENCE 전략과 최적화
- SEQUENCE 전략은 데이터베이스 시퀀스를 통해 식별자를 조회하는 추가 작업이 필요합니다. 따라서 데이터베이스와 2번 통신합니다.
- JPA는 시퀀스에 접근하는 횟수를 줄이기 위해 @SequenceGenerator.allocationSize를 사용합니다.
- allocationSize가 50인 경우 시퀀스를 한 번에 50 증가시킨 다음 1 ~ 50까지는 메모리에서 식별자를 할당합니다. 이후 51이 되면 시퀀스의 값을 100으로 증가시킨 이후 51 ~ 100까지 메모리에서 식별자를 할당하여 최적화를 수행합니다.
- 이 방법은 시퀀스 값을 선점하므로 여러 JVM이 동시에 동작해도 기본 키 값이 충돌하지 않는다는 장점이 있지만, 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한번에 많이 증가한다는 점을 염두해두어야 합니다.
이런 상황이 부담스럽고, INSERT 성능이 중요하지 않은 경우 allocationSize 값을 1로 설정하면 됩니다. - 위 최적화 방법은 hibernate.id.new_generator_mappings 속성을 true로 설정해야 적용되며, 설정하지 않는 경우 과거 hibernate가 사용하던 최적화 방법을 사용합니다.
( 과거에는 시퀀스 값을 하나씩 할당받고 애플리케이션에서 allocationSize 만큼 사용했습니다. )
TABLE 전략
- TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략입니다.
TABLE 전략을 사용하는 방법
1. 데이터베이스에 키 생성 용도로 사용할 테이블 생성
CREATE TABLE MY_SEQUENCES (
sequence_name varchar(255) not null, // 시퀀스 이름으로 사용
next_val bigint, // 시퀀스 값으로 사용
primary key ( sequence_name )
)
2. 데이터베이스의 테이블과 엔티티 매핑
@Entity
@TableGenerator( // 테이블 키 생성기 등록
name = "BOARD_SEQ_GENERATOR", // 테이블 키 생성기 이름
table = "MY_SEQUENCES", // 키 생성용 테이블과 매핑
pkColumnValue = "BOARD_SEQ", allocationSize = 1 // 시퀀스 값 컬럼명 )
public class Board{
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "BOARD_SEQ_GENERATOR" )
private Long id;
...
}
- TABLE 전략은 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 전략과 내부 동작방식이 같습니다.
- 키 생성 용도 테이블에 키로 사용할 값 이름으로 BOARD_SEQ 시퀀스 값으로는 식별자 값이 들어갑니다.
@TableGenerator
속성 | 기능 | 기본값 |
name | 식별자 생성기 이름 | 필수 |
table | 키 생성 테이블 명 | hibernate_sequences |
pkColumnName | 시퀀스 컬럼명 | sequence_name |
valueColumnName | 시퀀스 값 컬럼명 | next_val |
pkColumnValue | 키로 사용할 값 이름 | 엔티티 이름 |
initialValue | 초기 값, 마지막으로 생성된 값이 기준이다. | 0 |
allocationSize | 시퀀스 한 번 호출에 증가하는 수 ( 성능 최적화에 사용됨 ) | 50 |
catalog, schema | 데이터베이스 catalog, schema 이름 | |
uniqueConstraints(DDL) | 유니크 제약 조건을 지정할 수 있다. |
TABLE 전략은 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값으로 증가시키기 위해 UPDATE 쿼리를 사용합니다. 이 전략은 SEQUENCE 전략과 비교해서 데이터베이스에 한 번 더 통신하는 단점이 있습니다.
TABLE 전략을 최적화하는 방법은 @TableGenerator.allocationSize를 사용할 수 있습니다.
이를 사용한 최적화 방법은 SEQUENCE 전략과 같습니다.
AUTO 전략
- AUTO 전략은 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택합니다. ( 오라클을 선택하면 SEQUENCE, MySQL을 선택하면 IDENTITY를 사용합니다. )
- @GeneratedValue 의 기본 값은 AUTO이며, 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 장점을 가지고 있습니다.
- 키 생성 전략이 아직 확정되지 않은 개발 초기 단계나 프로토타입 개발 시 편리하게 사용할 수 있습니다.
- AUTO를 사용할 때 SEQUENCE나 TABLE 전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어 두어야 하며, 스키마 자동 생성 기능을 사용한다면 하이버네이트가 기본값을 사용하여 적절한 시퀀스나 키 생성 테이블을 만들어 줄 것입니다.
권장하는 식별자 선택 전략
- 데이터베이스 기본 키는 다음 3가지 조건을 모두 만족해야 합니다.
- null 값은 허용하지 않는다.
- 유일해야 한다.
- 변해선 안 된다.
- 테이블 기본 키를 선택하는 전략은 크게 2가지가 있습니다.
- 자연 키 ( natural key )
- 비즈니스에 의미가 있는 키 ( 주민등록번호, 이메일 , 전화번호 등 )
- 대리 키 ( surrogate key )
- 비즈니스와 관련 없는 임의로 만들어진 키, 대체 키로도 불립니다.
- ( 오라클 시퀀스, auto_increment, 키 생성 테이블 사용 )
- 자연 키 ( natural key )
자연 키의 경우 미래에 예측할 수 없는 요구사항이나 비즈니스의 변경
( 정책이 변하여 주민등록번호를 저장하지 못하는 등 )으로 인해 기본 키의 조건을 만족하지 못할 수 있습니다.
따라서, 비즈니스와 무관한 임의의 값인 대리 키를 사용하는 것을 권장합니다.
'JPA' 카테고리의 다른 글
[ JPA ] 영속성 컨텍스트와 엔티티 생명주기 (0) | 2023.08.01 |
---|