🟩 기본 키 매핑 (@Id)
@Id 어노테이션은 Entity 클래스에서 사용되고, 해당 클래스의 기본 키 필드를 지정하는데 사용된다.
🤔 기본키?
데이터베이스 테이블에서 각 행을 고유하게 식별하는데 사용되는 필드라고 생각하자.
아래와 같이 사용할 수 있다.
@Entity
public class Member {
@Id
private Long id;
public Member() {
}
}
🟩 JPA가 제공하는 데이터베이스 기본 키 생성 전략
생각해보면 데이터베이스마다 기본 키를 생성하는 방식이 다르다.
생성 방식이 다른 문제를 해결하기 위해서 JPA는 여러 기본 키 생성 전략이 존재한다.
기본 키 생성 전략을 크게 나누면 직접 할당, 자동 할당이 있다.
직접 할당은 단순히 @Id 어노테이션만을 이용하여 애플리케이션 내부에서 직접 주입한다는 의미이다.
자동 할당은 개발자가 직접 주입하지 않고 자동으로 주입한다는 의미이고 다음과 같은 3가지 전략(IDENTITY, SEQUENCE, TABLE)이 존재한다.
🟩 기본 키 직접 할당
기본 키 직접 할당 전략
- em.persist()를 호출하기 전(엔티티를 저장하기 전)에 애플리케이션에서 기본 키를 직접 할당하는 전략이다.
@Id 어노테이션 적용이 가능한 자바 타입은 다음 표와 같다.
@Id 적용 가능한 자바 타입 | 자바 기본형 |
자바 래퍼형 | |
String | |
java.util.Date | |
java.sql.Date | |
java.math.BigDecimal | |
java.math.BigInteger |
기본 키 직접 할당 예시 코드
Member member = new Member();
member.setId(1L); // 기본 키 직접 할당
em.persist(member);
🟩 기본 키 자동 할당
자동 생성 전략 사용
- @Id 뿐만 아니라 @GeneratedValue 어노테이션을 추가한다.
@GeneratedValue에 줄 수 있는 키 생성 전략
- IDENTITY : 기본 키 생성을 데이터베이스가 수행한다.
- SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.
- TABLE : 키 생성 테이블을 만들어 사용한다.
🤔 자동 생성 전략이 왜 다를까?
자동 생성 전략이 다른 이유는 데이터베이스 벤더마다 지원하는 방식이 다르기 때문이다.
01. IDENTITY 전략
IDENTITY 전략은 대부분 데이터베이스에서 기본 키 값을 자동으로 생성하는 전략이다.
테이블에 새로운 행이 등록될 때, 데이터베이스 시스템이 제공하는 자동 증가 시스템(AUTO_INCREMENT)를 이용하여 기본 키 값을 1씩 증가하도록 한다.
💡 01-01. IDENTITY 전략을 이용하는 데이터베이스 목록
전략 | 데이터베이스 목록 |
IDENTITY | MySQL |
PostgreSQL | |
SQL Server | |
DB2 |
💡01-02. IDENTITY 전략 매핑
@GeneratedValue 어노테이션의 속성을 IDENTITY로 할당한다.
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public Member() {
}
public Member(Long id) {
this.id = id;
}
}
이제 테이블 생성될 때 다음과 같이 AUTO_INCREMENT 속성이 들어갔다는 것을 확인할 수 있다.
CREATE TABLE member (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
💡 01-03. IDENTITY 전략 최적화
IDENTITY 전략은 데이터를 데이터베이스에 삽입한 후에 기본 키 값을 조회할 수 있다.
때문에 JPA는 ENTITY에 식별자값을 할당하기 위해서 데이터베이스를 추가적으로 조회한다.
추가적인 조회로 인한 비용을 줄이기 위해서는 JDBC3에 추가된 Statement.getGeneratedKeys()를 사용하면 된다.
데이터를 저장하면서 동시에 생성된 기본 키 값을 얻어오기 때문에 데이터베이스와 한 번만 통신한다.
하이버네이트(hibernate)는 이미 Statement.getGeneratedKeys() 메서드를 이용해서 데이터베이스와 한 번만 통신하고 있다.
💡 01-04. IDENTITY 식별자 생성 전략은 쓰기 지연이 동작하지 않는다.
IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있다.
Entity는 식별자를 가져야 영속 상태가 되어야 하므로 em.persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달되게 된다.
이러한 이유로 IDENTITY 전략에서는 쓰기 지연이 동작하지 않는다.
02. SEQUENCE 전략
SEQUENCE 전략은 일부 데이터베이스에서 기본 키 값을 자동으로 생성하는 방식이다.
시퀀스라는 객체를 사용하여 고유한 순차적인 값을 생성하여 기본 키 값으로 이용한다.
💡 02-01. SEQUENCE 전략을 이용하는 데이터베이스 목록
전략 | 데이터베이스 목록 |
SEQUENCE | Oracle |
PostgreSQL | |
DB2 | |
H2 |
💡02-02. SEQUENCE 전략 매핑
@GeneratedValue 어노테이션의 속성을 SEQUENCE로 할당한다.
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
public Member() {
}
public Member(Long id) {
this.id = id;
}
}
이제 Member 테이블 생성될 때 다음과 같이 SEQUENCE TABLE이 생성되었다는 것을 확인할 수 있다.
💡02-03. SEQUENCE 테이블 사용자 정의
데이터베이스 시퀀스를 따로 매핑하고 싶으면 @SequenceGenerator 어노테이션을 이용하자.
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ_2",
initialValue = 1,
allocationSize = 50
)
@Entity
public class Member {
@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR"
)
private Long id;
public Member() {
}
public Member(Long id) {
this.id = id;
}
}
DDL 쿼리 내역을 보면 SEQUENCE TABLE 이름이 "MEMBER_SEQ_2"로 생성된 것을 확인할 수 있다.
💡02-04. SEQUENCE와 IDENTITY 전략의 내부 동작 차이
SEQUENCE를 사용하는 코드와 IDENTITY와 사용하는 코드는 같다.
하지만 내부 동작이 다르다.
SEQUENCE의 경우
엔티티를 영속화 시키기위해 em.persist()를 호출하기 전에 데이터베이스 시퀀스를 사용해서 식별자를 조회한다.
조회한 식별자를 엔티티에 할당한 후에 영속성 컨텍스트에 저장하는 방식이다.
이후에 COMMIT하여 flush가 일어날때 엔티티를 데이터베이스에 저장한다.
IDENTITY의 경우
엔티티를 먼저 데이터베이스에 저장한 후 식별자를 조회해서 엔티티에게 할당한다.
💡 02-05. SEQUENCE 전략 최적화
SEQUENCE 전략은 데이터베이스 시퀀스를 통해 식별자를 조회하는 추가 작업이 있기 때문에 데이터베이스를 2번 조회하게 된다.
JPA는 이러한 2번 조회하는 비용을 해결하기 위해서 @SequenceGenerator.allocationSize를 사용한다.
allocationSize(기본값 : 50)를 설정한 값만큼 한 번에 시퀀스를 증가시키고 메모리에 할당한다.
단점이 있다면 처음에 단 하나의 데이터를 저장한다고 했을 때 한 번에 50개를 증가시켜야하는 점이다.
성능상 문제는 없지만 부담스럽다면 allocationSize를 줄여서 사용하자.
03. TABLE 전략
TABLE 전략은 키 생성 전용 테이블을 만들어서 컬럼을 만들어서 데이터베이스 시퀀스를 비슷하게 사용하는 전략이다.
(시퀀스 대신에 테이블을 사용한다는 것만 SEQUENCE 전략과 다르다.)
TABLE 전략은 모든 데이터베이스에서 사용 가능하다는 장점이 있다.
💡03-01. TABLE 전략 사용 예시
키 생성 용도로 사용할 테이블을 만든다.
CREATE TABLE MY_SEQUENCES (
sequence_name VARCHAR(255) NOT NULL,
next_value BIGINT,
PRIMARY KEY (sequence_name)
);
@TableGenerator 어노테이션을 이용해서 테이블 키 생성기(MEMBER_SEQ_GENERATOR)를 등록하고
키 생성용 테이블(MY_SEQUENCES)을 매핑해서 사용한다.
@Entity
@TableGenerator(
name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "MEMBER_SEQ", allocationSize = 50
)
public class Member {
@Id
@GeneratedValue(
strategy = GenerationType.TABLE
generator = "MEMBER_SEQ_GENERATOR"
)
private Long id;
}
💡 03-02. @TableGenerator 속성
속성 | 기능 | 기본값 |
name | 식별자 생성기 이름 | 필수 |
table | 키생성 테이블명 | hibernate_sequences |
pkColumnName | 시퀀스 컬럼명 | sequence_name |
valueColumnName | 시퀀스 값 컬럼명 | next_val |
pkColumnValue | 키로 사용할 값 이름 | 엔티티 이름 |
initialValue | 초기 값, 마지막으로 생성된 값 기준 | 0 |
allocationSize | 시퀀스 한 번 호출에 증가하는 수 | 50 |
catalog, schema | 데이터베이스 catalog, schema 이름 | |
uniqueConstraints(DDL) | 유니크 제약 조건을 지정할 수 있다. |
💡 03-03. TABLE 전략 최적화
SEQUENCE 전략과 비교해서 데이터베이스와 한 번 더 통신하는 단점이 있다.
TABLE 전략을 최적화하기 위해서는 @TableGenerator.allocationSize를 사용하면 되는데 이는 SEQUENCE 전략 최적화 방법과 동일하다.
👊 정리하자면
- 직접 할당 : 영속화 시키기 이전에 애플리케이션에서 직접 식별자 값을 할당한다.
- IDENTITY : 데이터베이스에 엔티티를 저장해서 식별자 값을 얻은 후 영속화 컨텍스트에 저장한다.
- SEQUENCE : 데이터베이스 시퀀스에서 식별자 값을 가져와 영속화 컨텍스트에 저장한다.
- TABLE : 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 얻어 영속성 컨텍스트에 저장한다.
🔗 참고