본문 바로가기
서버/스프링

[JPA] 기본 키 매핑 전략 정리

by 베어 그릴스 2022. 10. 30.
320x100

*본 게시글은 김영한님의 자바 ORM JPA 표준 책을 보고 이해한 내용을 바탕으로 정리한 글입니다.

 

JPA에서 엔티티의 기본 키가 데이터베이스의 기본 키에 매핑되는 여러 전략을 알아보자.

 

기본 키 자동 생성 전략이 다양한데, 이 이유는 데이터베이스 벤더마다 지원하는 방식이 모두 다르기 때문이다.

예를 들어 오라클 데이터베이스는 시퀀스를 제공하지만 MYUSQL은 시퀀스를 제공하지 않는다.

 

즉, SEQUENCE나 IDENTITY는 데이터베이스에 의존하는 전략이다. 데이터베이스에 의존하지 않으려면 TABLE 전략을 사용하여야 한다.

 

각 전략의 자세한 내용은 아래를 참고하자.

 

1. 직접 할당

@Id
private Long id;

별다른 어노테이션 없이 @Id 어노테이션만 붙이면 직접 할당 전략을 사용한다.

 

em.persist()로 엔티티를 저장하기 전에 애플리케이션에서 기본 키를 직접 할당하는 방법이다.

 

당연하겠지만, 식별자 값 없이 저장하면 예외가 발생한다.

 

 

2. 자동 생성 : IDENTITY

기본 키 생성을 데이터베이스에 위임하는 전략이다.

주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다. (ex: MYSQL의 AUTO_ INCREMENT)

@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
private Long id;

기본 키 생성을 데이터베이스에 위임하면 보통 쿼리가 나가는 시점은 트랜잭션 커밋 시점이므로 그 이후에 ID 값을 알 수 있다.

 

*주의 : 엔티티가 영속 상태가 되려면 식별자가 반드시 필요하다. IDENTITY 전략을 생각해보자. 엔티티가 무조건 데이터베이스에 저장되어야만이 ID 값을 구할 수 있으므로 em.persist()와 동시에 INSERT SQL이 데이터베이스에 전달된다. 즉, 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.

 

3. 자동 생성 : SEQUENCE

데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다.

오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용한다.

 

시퀀스를 사용하기 위해선 데이터베이스에 우선 시퀀스를 생성해야 한다.

CREATE SEQUENCE MEMBER_SEQ START WITH 1 INCREMENT BY 1;

이후 @SequenceGenerator 어노테이션을 통해 엔티티의 기본키와 매핑해준다.

@Entity 
@SequenceGenerator( 
 name = “MEMBER_SEQ_GENERATOR", 
 sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
 initialValue = 1, allocationSize = 1) 
public class Member { 
 @Id 
 @GeneratedValue(strategy = GenerationType.SEQUENCE, 
 generator = "MEMBER_SEQ_GENERATOR") 
 private Long id;

IDENTITY 전략과 마찬가지로 em.persist() 시에 영속성 컨텍스트에 식별자 값이 필요하므로 데이터베이스에 접근이 필요하다. 

 

그래서 em.persist()가 불리면 먼저 데이터베이스 시퀀스를 사용해서 식별자를 조회한다.

그리고 조회한 식별자를 엔티티에 할당한 후에 엔티티를 영속성 컨텍스트에 저장하고, 이후 트랜잭션 커밋 시 플러시가 일어나면 엔티티를 데이터베이스에 저장한다.

 

 

SEQUENCE 전략 최적화

SEQUENCE 전략은 데이터베이스 시퀀스를 통해 식별자를 조회하는 추가 작업이 필요하므로 데이터베이스와 2번 통신한다. (식별자 조회, INSERT 쿼리)

 

JPA는 접근 횟수를 줄이기 위해 allocationSize를 사용한다.

allocationSize에 설정한 값만큼 한 번에 시퀀스 값을 증가시키고 나서 메모리에 시퀀스 값을 할당한다. 즉, 50으로 할당되어 있으면 시퀀스를 한 번에 50 증가 시킨 다음 1~50 까지는 메모리에서 식별자를 할당한다.

 

이 최적화 방법은 시퀀스 값을 선점하므로 여러 JVM이 동시에 동작해도 기본 키 값이 충돌하지 않는 장접이 있다. 반면 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한번에 많이 증가한다는 점을 염두에 두어야 한다.

 

 

4. 자동 생성 : TABLE

키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략이다.

모든 데이터베이스에 적용 가능하나 테이블을 추가로 생성하고 성능적인 측면에서 단점이 있다.

 

먼저 키 생성 용도로 사용할 테이블을 만들어야 한다.

create table MY_SEQUENCES ( 
 sequence_name varchar(255) not null, 
 next_val bigint, 
 primary key ( sequence_name ) 
)

 

시퀀스 전략과 마찬가지로 해당 전략을 사용할 엔티티와 매핑시켜주어야 한다.

@Entity 
@TableGenerator( 
	name = "MEMBER_SEQ_GENERATOR", 
	table = "MY_SEQUENCES", 
	pkColumnValue = “MEMBER_SEQ", allocationSize = 1) 
public class Member { 
	@Id 
	@GeneratedValue(strategy = GenerationType.TABLE, 
	generator = "MEMBER_SEQ_GENERATOR") 
	private Long id;
    }

테이블 전략은 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 시퀀스 전략과 내부 동작 방식이 전부 같다.

즉, 똑같이 allocationSize를 통해 미리 할당할 수 있다.

 

*참고 : TABLE 전략은 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값으로 증가시키기 위해 UPDATE 쿼리를 사용한다. 이를 방지하기 위해 allocationSize를 사용하여 한번에 UPDATE 하고 메모리에서 증가시킨 식별자를 사용할 수 있다.

 

5. AUTO 전략

@GenreatedValue(stratgy = GenerationType.AUTO)를 사용하면 선택한 데이터베이스 방언에 따라 위 3개 자동생성 전략 중 하나를 자동으로 선택한다. 예를 들어, 시퀀스가 없는 MYSQL이면 IDENTITY, 시퀀스가 있는 오라클이면 SEQUENCE를 선택한다.

즉, 데이터베이스를 변경해도 코드를 수정할 필요가 없다.

 

그러나 시퀀스 전략이나 테이블 전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어 두어야 한다.

만약, 스키마 자동 생성 기능을 사용한다면 하이버네이트가 기본값을 사용해서 적절한 시퀀스나 테이블을 만들어준다.

 

 

*주의!

기본 키는 변하면 안되는 값이므로 변경 시 JPA에서 에러를 발생시킨다. 즉, setId() 같은 변경 메소드를 만들지 않거나 외부에 공개하지 않도록 해야 한다.

728x90