JPA에서 Id 생성하기
jpa를 사용해서 entity를 만들다보면 @Id 어노테이션을 사용하게된다. 이때 db에 id생성 방식을 잘 만들었다면 자동할당이 되서 신경쓰지 않아도돠나 직접할당을 사용하려면 @GeneratedValue를 사용해서 stretagy 옵션을 사용해서 설정할수 있다. 여기올수있는 값의 코드는 다음처럼 생겼다.
Enum값으로 4가지가 있다
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GeneratedValue {
GenerationType strategy() default GenerationType.AUTO;
String generator() default "";
}
public enum GenerationType {
AUTO,
IDENTITY,
SEQUENCE,
TABLE
}
IDENTITY
IDENTITY 전략은 ID 생성을 DB에게 전적으로 위임하는 방식이다.
대표적으로 MySQL, PostgreSQL에서 사용하는 AUTO_INCREMENT 기능이 이 전략에 해당된다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
이 방식의 특징은 단순하지만 제약이 있다는 것이다.
em.persist()를 호출하면 바로 insert 쿼리가 날아가고, 그 결과로 DB가 생성한 ID 값을 받아온다.
즉, ID를 먼저 알아야 영속성 컨텍스트에 넣을 수 있기 때문에 insert를 지연시킬 수 없다.
그로 인해 IDENTITY 전략은 쓰기 지연(batch insert) 을 사용할 수 없고,
성능 최적화가 필요한 경우에는 불리할 수 있다.
Hibernate에서는 내부적으로 Statement.RETURN_GENERATED_KEYS를 통해 ID 값을 다시 받아온다.
SEQUENCE
SEQUENCE 전략은 DB의 시퀀스 객체를 이용해 ID를 생성하는 방식이다.
Oracle, PostgreSQL처럼 시퀀스를 지원하는 DB에서만 사용 가능하다.
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq_gen")
@SequenceGenerator(
name = "user_seq_gen",
sequenceName = "user_seq",
allocationSize = 1
)
private Long id;
이 방식은 JPA가 먼저 DB 시퀀스로부터 ID 값을 select 해서 가져온 후
그 ID를 엔티티에 할당하고 영속성 컨텍스트에 등록하는 방식이다.
즉, IDENTITY처럼 곧바로 insert가 나가는 게 아니라,
ID만 미리 확보하고 나중에 flush 시점에 insert가 이루어진다.
CREATE SEQUENCE user_seq START WITH 1 INCREMENT BY 1;
allocationSize는 시퀀스 전략 쓸 때 ID를 몇 개씩 미리 땡겨올지 정하는 옵션이다.
이걸 잘 못 설정하면 삽질한다.
예를 들어 allocationSize=50이면 JPA는 한 번 시퀀스 테이블 쿼리해서 1~50까지 ID를 메모리에 들고 있는다.
그다음부터는 insert할 때마다 DB 안 들락거리고 메모리에서 꺼내 쓴다. 당연히 성능 좋아진다.
근데 반대로 allocationSize=1이면 insert 한 번 할 때마다 시퀀스 테이블 쿼리 날린다.
성능? 그냥 끝났다 생각하면 된다.
TABLE
TABLE 전략은 말 그대로 ID 생성을 위한 전용 테이블을 만들어서 ID 값을 관리하는 방식이다.
시퀀스 기능이 없는 MySQL 같은 DB에서도 사용할 수 있다는 게 가장 큰 장점이다.
사용 방법은 간단하다. @GeneratedValue(strategy = GenerationType.TABLE, generator = "gen_name") 어노테이션을 붙이고, 그 아래 @TableGenerator를 통해 어떤 테이블을 쓸지 정의해주면 된다.
예를 들어 다음처럼 작성할 수 있다.
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "user_id_gen")
@TableGenerator(
name = "user_id_gen",
table = "id_generator",
pkColumnName = "entity_name",
valueColumnName = "next_id",
pkColumnValue = "user",
allocationSize = 1
)
private Long id;
private String name;
}
이렇게 하면 JPA는 id_generator라는 테이블에서 entity_name = 'user'인 row를 찾아 next_id 값을 ID로 쓰고, 그걸 1 증가시킨다.
이 테이블은 개발자가 직접 만들어야 하며, 아래처럼 구성한다.
CREATE TABLE id_generator (
entity_name VARCHAR(50) NOT NULL PRIMARY KEY,
next_id BIGINT NOT NULL
);
INSERT INTO id_generator(entity_name, next_id) VALUES ('user', 1);
단점은 분명하다. ID를 하나 뽑기 위해 테이블을 조회하고 수정하는 트랜잭션을 매번 해야 하기 때문에 속도가 느리고, 동시성이 높을 경우 병목이 생길 수 있다.
그래서 성능이 중요한 상황에서는 잘 쓰지 않고, DB 독립성이 필요한 경우에 한해 제한적으로 사용한다.