기본 키 매핑
@Id 어노테이션만 사용하면 기본 키를 애플리케이션에서 직접 할당한다. 기본 키를 애플리케이션에서 직접 할당하는 방식 대신에 오라클의 시퀀스 오브젝트나 MySQL의 AUTO_INCREMENT 같은 데이터베이스가 생성해 주는 값을 이용하려면 어떻게 해야 할까?
JPA에서 제공하는 데이터베이스 기본 키 생성 전략
JPA가 제공하는 DB 기본 키 할당 전략은 직접 할당 방식, 자동 생성 방식 두 가지가 있다.
직접 할당 방식은 애플리케이션이 기본 키를 직접 할당하는 방식이다. 자동 생성 방식은 대리키를 사용하는 방식으로 AUTO, IDENTITY, SEQUENCE, TABLE 네 가지 옵션이 있다.
해당 방식들은 DB에 의존되는 방식이다.
package jakarta.persistence;
public enum GenerationType {
TABLE,
SEQUENCE,
IDENTITY,
UUID,
AUTO;
private GenerationType() {
}
}
직접 할당 방식
기본키를 직접 할당하는 방식으로 간단하게 @Id를 엔티티의 Key 컬럼의 사용해 주면 된다.
@Id
private long id;
@Id 가 적용 가능한 타입은 다음과 같다.
- 자바 기본형(int, double, long...)
- 자바 Wrapper형
- String
- java.util.date
- java.sql.date
- java.math.BigDecimal
- java.math.BigInteger
기본 키 직접 할당 전략은 em.persist()로 엔티티를 저장하기 전에 애플리케이션에서 기본 키를 직접 할당해 주는 방식이다.
Board board = new Board();
board.setId(1L); // 직접 할당
em.persist(board);
자동 생성 방식
자동 생성 방식을 사용할 경우 @Id와 @GeneratedValue를 사용한다.
IDENTITY 전략
기본 키 생성을 DB에 위임하는 전략이다. 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용되고 단적인 예로 MySQL의 AUTO_INCREMENT 기능을 제공한다.기본 키 값을 자동으로 생성될 수 있는 DBMS에서 사용된다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
IDENTITY 전략은 데이터를 데이터베이스에 INSERT 한 후에 기본키 값이 조회 가능하다. 따라서 엔티티의 식별자 값을 조회하려면 추가로 데이터베이스 조회가 필요하다.
JPA는 영속성 컨텍스트의 쿼리(쓰기 지연)를 트랜잭션 커밋 시점에 데이터베이스의 전달하게 되는데 IDENTITY는 예외적으로 persist 시 INSERT 쿼리를 실행하게 된다. 왜냐하면 데이터베이스의 지정된 식별자값을 활용하는 방식이기 때문이다. 이 후 추가 데이터베이스의 조회를 통해 식별자 값을 가져와야 한다. JDBC 내부에서 INSERT SQL 실행 이후 ID값을 알 수 있는데 JPA는 그것을 이용해 식별자 값을 영속성 컨텍스트의 추가하여 가져올 수 있게 된다.
Member member = new Member();
member.setUsername("h1");
// SQL문이 언제 실행되는지 확인해보자.
System.out.println("===========");
em.persist(member);
System.out.println("member.id = " + member.getId); 이 시점에서 ID값을 가져올 수 있다.
System.out.println("===========");
결론적으로 IDENTITY 전략에서는 모아서 한 번의 SQL을 전달하는 쓰기 지연이 동작하지 않는다. 단점이라고도 볼 수 있지만 쓰기 지연 같은 버퍼링 전략이 속도면에서 이점이 크지 않기 때문에 무리 없이 사용할 수 있다.
SEQUENCE 전략
데이터베이스 시퀀스는 유일한 값을 순서대로 생성해 주는 특별한 데이터베이스 오브젝트이다.
SEQUENCE 전략은 데이터베이스 시퀀스를 활용하여 기본키를 할당해 주는 방식이다. 이 전략은 DB 내에서 시퀀스를 지원하는 Oracle, PostgreSQL, DB2, H2 DB에서 주로 사용된다.
다음은 엔티티 설정 방법의 예시이다.
@Entity
@SequenceGenerator(
name = "USER_SEQ_GENERATOR"
, sequenceName = "USER_SEQ"
, initialValue = 1
, allocationSize = 1
)
public class User {
@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE
, generator = "USER_SEQ_GENERATOR"
)
private long id;
}
데이터베이스의 시퀀스를 이용하는 방식이기 때문에 시퀀스 생성기 어노테이션인 @SequenceGenerator를 이용하여 속성을 지정하면 데이터베이스 시퀀스가 매핑이 되게 된다.
이후 Key 칼럼의 @GeneratedValue 속성의 generator를 매핑시키면 된다. (@SequenceGenerator name 속성)
시퀀스 동작 예시이다.
Member member = new Member();
member.setName("h1");
em.persist(member);
tx.commit();
// 우선 시퀀스를 생성한다.
Hibernate:
drop sequence if exists USER_SEQ
Hibernate:
create sequence USER_SEQ start with 1 increment by 1
// 시퀀스 오브젝트에서 값을 조회 후 INSERT를 한다.
Hibernate:
select
next value for USER_SEQ
Hibernate:
/* insert for
hellojpa.Member */insert
into
Member (date, MEMBER_NAME, time, timestamp, id)
values
(?, ?, ?, ?, ?)
@SequenceGenerator 어노테이션 속성들은 아래와 같다.
name | 식별자 생성기 이름 | 없음 지정 필수. |
sequenceName | DB에 등록되어 있는 Sequence이름 | hibernate_sequence |
initalValue(DDL) | DDL 생성시에만 사용, Sequence DDL 생성시 처음 시작 value를 설정 | 1 |
allocationSize | Sequence 한번 호출시 증가하는 수(성능 최적화에 사용) | 50 |
catalog, schema | DB catalog, schema 이름 |
여기서 주의할 점은 allocationSize의 기본값이 50이라는 점이다. JPA는 성능 이슈 때문에 기본값을 50으로 설정되어 있다. 1로 설정되어 있으면 호출할 때마다 시퀀스 next call이 호출되어 데이터베이스가 지속적으로 호출되는 문제가 발생하여 성능 이슈가 생기게 된다.
allocationSize에 설정 값만큼 한 번에 데이터 베이스 sequence를 증가시키고,그만큼 Memory에 seqeunce 값을 할당한다.
그 후 Memory를 활용해 JVM안에서 sequence를 할당하여 사용한다.
이 방법은 시퀀스를 미리 선점하고 있는 방식이라 여러 서버(여러 JVM) 환경에서도 동시성 이슈없이 사용이 가능하다. 물론 서버가 내려가면 메모리가 초기화되어 선점한 만큼 빈 공간이 생기므로 너무 큰 수를 지정하는 것보단 50, 100 정도로 권장된다.
INDENTITY와 다르게 쓰기 지연?
이 방식을 써보면 알겠지만 INSELT SQL 나오기 전에 시퀀스를 조회하는 것을 알 수 있다. 시퀀스 조회 시 데이터베이스 시퀀스가 실행되지만 거기서 가져온 ID값을 영속성 컨텍스트에 저장하기 때문에 INSERT문을 트랙잭션 쓰기 지연과 마찬가지로 커밋시점에 실행되게 된다.
Table 전략
Table 전략은 마치 데이터베이스 시퀀스처럼 키 생성 전용 테이블을 하나 만들어서 사용하는 방식이다. 이 전략은 테이블을 생성하여 사용하므로 모든 DB에서 사용할 수 있는 장점이 있고 단점으로는 성능이 떨어진다.
@TableGenerator에서 사용 가능한 속성들은 아래와 같다.
name | 식별자 생성기 이름 | 없음 지정 필수. |
table | 키 생성 테이블 명 | hibernate_sequences |
pkColumnName | 시퀀스 컬럼 명 | sequence_name |
valueColumnName | 시퀀스 값 컬럼 명 | next_val |
pkColumnValue | 키로 사용할 값 이름 | Entity 이름 |
initialValue | 초기 값, 마지막 생성된 값이 기준 | 0 |
allocationSzie | 시퀀스 호출시 증가 값(성능 최적화에 사용) | 50 |
catalog, schema | DB catalog, schema 이름 | |
uniqueConstraints(DDL) | 유니크 제약 조건을 지정 |
엔티티 적용 예시이다.
@Entity(name = "Member")
@TableGenerator(
name = "USER_SEQ_GENERATOR"
, table = "CUSTOM_SEQUENCE"
, pkColumnValue = "USER_SEQ"
, allocationSize = 1
)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "USER_SEQ_GENERATOR")
private Long id;
....
}
@TableGenerator를 사용해서 테이블 키 생성기를 등록한다. 등록하면 아래와 같이 DB table이 생성되며 @GeneratedValue.generator를 이용해 @TableGenarator.name과 매핑하여 사용하면 된다.
create table CUSTOM_SEQUENCE (
next_val bigint,
sequence_name varchar(255) not null,
primary key (sequence_name)
)
해당 전략은 SEQUENCE 전략과 내부 동작 방식이 동일하다. 하지만 아래와 같이 TABLE 전략은 값을 조회하면서 Select Query를, 그 후 값 증가를 위해 Update Query를 사용한다.
SEQUENCE 방식과 비교해 DB를 한번 더 통신하는 단점이 있고, DB마다 ID 생성 방식이 제공되는 만큼 운영에서는 실질적으로 사용되기는 어렵다.
Hibernate:
select
tbl.next_val
from
CUSTOM_SEQUENCE tbl
where
tbl.sequence_name=? for update
Hibernate:
update
CUSTOM_SEQUENCE
set
next_val=?
where
next_val=?
and sequence_name=?
// 테이블 이기 때문에 조회 후 업데이트 과정이 있다.
AUTO
AUTO 전략은 선택한 DB 방언에 따라 자동으로 IDENTITY, SEQUENCE, TABLE 전략을 자동으로 선택한다.
해당 전략은 자동으로 선택되니 수정을 최소화할 수 있는 장점이 있어 초기 DB와 Key 전략이 정해지지 않았다면 편리하게 사용될 수 있다.
하지만 버전에 따라 생성 방식이 달라질 수 있고 SEQUECE나 TABLE 전략이 선택되었다면, 데이터베이스 시퀀스나 생성 키 테이블을 생성해줘야 한다. (물론 DDL을 이용해 생성하면 하이버네이트가 기본 값으로 자동 생성이 된다.)
권장하는 식별자 전략
기본키 제약 조건을 우선 생각해 보면 null 값이며 안되며, 유일해야 되고 변하면 안 된다가 기본 원칙이다.
테이블의 기본키는 두 가지로 나뉘게 된다. 하나는 자연키로 주민등록번호, 이메일 등 실질적으로 존재하는 값들을 이용해 키매핑을 하는 것을 의미한다. 다음은 대리 키(대체 키)로 비즈니스와 관련 없는 임의로 만들어진 키로 오라클의 시퀀스나 MySQL의 Auto_Increament 등이 있다.
자연키 보단 대리키를 이용하자.
자연키의 가장 큰 문제는 미래까지 식별자로써 연락을 할 수 있냐는 문제가 발생되기 때문이다. 예를 들어 주민등록번호를 자연키로 지정했는데 법률상 문제로 주민등록번호를 가지고 있을 수 없는 환경으로 변경되며 더 이상 주민등록번호는 식별자 역할을 할 수 없게 된다.
설계 시에는 큰 무리 없어 보이지만 비즈니스 규칙을 생각보다 쉽게 변하게 된다. 그러므로 그러한 변화와 상관이 없는 대리키 사용을 권장한다.
JPA는 기본적으로 모든 엔티티에 일관된 방식으로 대리 키 사용 방식을 권장한다.
권장 방식
권장 방식으로는 넓은 범의가 케어가능한 Long형을 쓰고 SEQUNCE나 INDENTITY 같은 키 생성 전략에 따라 대체 키를 사용한다.
Long 형 + 대체키 + 키 생성 전략
참고자료
'Framework > JPA' 카테고리의 다른 글
[JPA] 객체 관계 매핑에 필요한 @JoinColum 속성 알아보기 (0) | 2024.04.26 |
---|---|
[연관관계 매핑 기초] 단방향 연관관계와 객체와 테이블의 차이 (0) | 2024.03.21 |
[JPA] 엔티티 매핑 - 필드와 컬럼 매핑 (0) | 2024.03.14 |
[JPA] 엔티티 매핑 - 데이터베이스 스키마 자동 생성 (0) | 2024.03.14 |
[JPA] 엔티티 매핑 - 객체와 테이블 매핑 (0) | 2024.03.14 |