이번 포스팅에서 엔티티 매핑의 핵심적인 역할을 하는 기본키(Primary Key)에 대해 살펴보겠습니다. 기본키는 데이터베이스에서 각 행을 식별하는 데 사용되는 고유한 식별자입니다. 이는 엔티티와 매핑된 객체를 고유하게 식별하고 관계를 맺을 때 중요한 역할을 합니다.
@Id
@Id는 엔티티의 주요 식별자를 표시하는 역할을 수행합니다. 즉 데이터베이스 테이블의 기본 키(pk)와 객체의 필드를 매핑시켜 줍니다. 일반적으로 @GeneratedValue와 같이 사용되며 @Id만 사용할 경우 기본 키를 직접 할당해주어야 합니다.
@Id를 적용할 수 있는 타입은 다음과 같습니다.
- 자바 기본형 (int, long, ...)
- 자바 래퍼형 (Integer, Long, ...)
- String
- Date (java.util), Date (java.sql)
- BigDecimal, BigInteger
@GeneratedValue
@Id가 식별자를 표시하는 목적이라면 @GeneratedValue는 기본 키를 자동으로 생성시켜 주는 역할을 합니다. 해당 애노테이션을 사용하면 데이터베이스가 전략에 따라 자동으로 기본 키를 할당해 줍니다.
@GeneratedValue의 속성은 전략(strategy)이 있으며, 이를 통해 자동 생성 방식을 설정할 수 있습니다.
속성 | 기능 | 대표적인 DBMS |
GenerationType.IDENTITY | 기본키 생성을 데이터베이스에 위임한다. | MySQL |
GenerationType.SEQUENCE | 시퀀스 사용 (@SequenceGenerator 설정 필요) | Oracle |
GenerationType.TABLE | 키 생성용 테이블 사용 (@TableGenerator 필요) | 모든 DBMS |
GenerationType.AUTO | 데이터베이스 방언에 따라 자동 지정 (기본값) |
1. GenerationType .IDENTITY
@GeneratedValue(strategy = GenerationType.IDENTITY)
IDENTITY 전략은 기본 키 생성을 데이터베이스에 위임하게 됩니다. 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용하며, MySQL의 auto_increament의 기능과 방식이 유사합니다.
영속성 컨텍스트는 호출할 쿼리를 쓰기 지연 sql 저장소에 보관하고, 트랜잭션이 커밋되는 시점에 한꺼번에 쿼리를 발생하는 것으로 배웠습니다. 그러나 IDENTITY 전략을 사용하면 트랜잭션이 커밋되는 시점이 아닌 em.persist() 호출 시 insert 쿼리가 발생하게 됩니다.
이러한 이유는 엔티티가 영속 상태가 되기 위해서는 식별자 즉, 기본 키를 가지고 있어야 하기 때문입니다. IDENTITY 전략을 사용하면 식별자를 데이터베이스에 저장하기 전까지 알 수 없기 때문에 즉시 SQL문이 데이터베이스에 반영되고 데이터베이스에서 식별자를 조회합니다. 따라서 IDENTITY 전략은 트랙잭션을 지원하는 쓰기 지연이 동작하지 않습니다.
2. GenerationType.SEQUENCE
@GeneratedValue(strategy = GenerationType.SEQUENCE)
SEQUENCE 전략은 Oracle의 sequence 처럼 유일한 값을 순서대로 생성합니다. 이 전략은 sequence를 지원하는 Oracle, PostgreSQL, DB2, H2에서 사용할 수 있습니다.
SEQUENCE 전략을 사용하기 위해서는 우선 사용할 데이터베이스 시퀀스를 매핑해야 합니다. @SequenceGenerator를 사용하여 시퀀스 생성기를 등록한 후, @GeneratedValue의 generator 속성으로 시퀀스 생성기를 선택합니다.
@Entity
@SequenceGenerator(
name = "MEMBER_SEQ_GENERATOR",
sequenceName = "MEMBER_SEQ",
initialValue = 1,
allocationSize = 50
)
public class Member {
@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR"
)
private Long id;
...
}
@SequenceGenerator 속성
속성 | 설명 | 기본값 |
name | 식별자 생성기 이름을 지정 | 필수 설정 |
squenceName | 데이터베이스에 등록되어 있는 시퀀스 이름을 설정 | hibernamte_sequnce |
initialValue | DDL 생성 시에만 사용됨, 시퀀스 DDL을 생성할 때 처음 1 시작하는 수를 지정 | 1 |
allocationSize | 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용) 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 1로 설정 |
50 |
catalog, shema | 데이터베이스 catalog, schema 이름 |
🤔 allocationSize의 작동방법?
- 최초 persist() 호출 시 데이터베이스의 시퀀스 nextValue를 두 번 호출하여 시작값과 끝값을 가져온다.
- 애플리케이션에서 시작값이 끝값이 될 때까지 시퀀스를 메모리에서 할당해 준다.
- 시퀀스를 끝값까지 전부 사용하게 되면 다시 시퀀스를 호출하는데, 여기서 JPA는 allocationSize를 보고 다음 시작값을 계산한다. 끝 값 = 현재 값 + allocationSize, 시작 값 = 끝 값 - (allocationSize - 1)
- 2번과 3번을 반복한다.
🤔 allocaitionSize 주의사항
allocaitionSize의 기본값은 50입니다. 만약 실제 데이터베이스의 시퀀스 증가값이 1일 때 어떤 일이 벌어질까요? 위의 작동 방법대로면, 3번 동작 즉, 두 번째 시작 값을 계산할 때 시작 값이 음수가 됩니다. 그리고 시작값부터 시퀀스가 계속 할당되고 끝값으로 가게 되면 언젠가는 이미 존재하는 시퀀스를 참조하게 되어 에러가 발생하게 됩니다. 고로, allocaitionSize를 설정할 때는 실제 데이터베이스의 증가값을 고려하여 시작값이 음수가 되지 않게 고려하며 성능 최적화를 해야 합니다.
🤔 IDENTITY와 SEQUENCE의 차이점?
두 전략은 식별자 값을 얻기 위해 DB에 접속하는 것이 비슷해 보이나 차이점이 있습니다. 서로 플러시(flush)가 발생되는 시점이 다릅니다.
IDENTITY 전략은 em.persist() 시점에 쓰기 지연을 하지 않고 insert 쿼리를 보내 데이터베이스에 데이터를 저장하고, 식별자를 조회한 후 영속성 컨텍스트에 저장됩니다.
SEQUENCE 전략은 em.persist() 시점에 시퀀스를 조회하는 쿼리를 DB에 날려 시퀀스의 nextValue를 받고 식별자를 조회하고 조회한 식별자를 엔티티에 할당한 후, 영속성 컨텍스트에 저장됩니다. 이후 트랜잭션 commit 시점에 플러시가 발생하여 데이터베이스에 저장합니다.
3. GenerationType.TABLE
@GeneratedValue(strategy = GenerationType.TABLE)
TABLE 전략은 키 생성 전용 테이블을 하나 만들고, 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내 내는 전략입니다. 따로 @TableGenerator 설정이 필요하며 이 전략은 테이블을 사용하기 때문에, 시퀀스를 지원하지 않는 데이터베이스에서도 사용할 수 있는 이점이 있습니다. 그러나 성능 때문에 권장되는 방식은 아닙니다.
@Entity
@TableGenerator(
name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCE",
pkColumnValue = "MEMBER_SEQ",
initialValue = 1,
allocationSize = 50
)
public class Member {
@Id
@GeneratedValue(
strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR"
)
private Long id;
...
}
SEQUENCE 전략과 매우 흡사하며, 시퀀스 대신 테이블을 사용하는 것 이외에는 내부 동작 방식이 동일합니다.
@TableGenerator 속성
속성 | 기능 | 기본값 |
name | 식별자 생성기 이름을 지정 | 필수 설정 |
table | 키 생성 테이블명을 설정 | hibernate_squences |
pkColumnName | 시퀀스 컬럼명을 설정 | hibernate_name |
valueColumnName | 시퀀스 값 컬럼명을 설정 | next_val |
pkColumnValue | 키로 사용할 값의 이름을 설정 | 엔티티 이름 |
initialValue | 초기값, 마지막으로 생성된 값의 기준 | 0 |
allocationSize | 시퀀스를 한번 호출할 때 증가하는 수 (성능 최적화에 사용된다.) | 50 |
catalog, schema | 식별자 생성기의 catalog, schema 이름 | |
uniqueConstraints | 유니크 제약 조건을 지정 |
4. GenerationType.AUTO
@GeneratedValue(strategy = GenerationType.AUTO)
데이터베이스는 종류도 많고 기본 키를 만드는 방법도 다양합니다. AUTO로 설정하면 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택하게 됩니다.
@GeneratedValue의 속성 strategy을 생략할 경우 기본 값은 AUTO로 설정되며, 만약 AUTO를 사용할 때 SEQUENCE나 TABLE 전략이 선택되면, 시퀀스나 키 생성용 테이블을 미리 만들어 두어야 합니다.
자바 ORM 표준 JPA 프로그래밍 - 기본편 | 김영한 - 인프런
김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도
www.inflearn.com
'Spring > JPA' 카테고리의 다른 글
프록시(Proxy) (0) | 2024.04.28 |
---|---|
상속관계 매핑 (0) | 2024.04.24 |
연관관계 매핑 정리 (0) | 2024.04.23 |
엔티티 매핑(Entity Mapping) - 객체와 테이블, 필드와 컬럼 (1) | 2024.04.21 |
영속성 컨텍스트(Perisistence Context) (0) | 2024.04.21 |