JPA

JPA 기본키 생성 전략, @GeneratedValue 사용시 주의점

까뮈_b 2022. 5. 1. 00:14

JPA로 테이블과 엔티티를 매핑할 때, 식별자로 사용할 필드 위에 @Id 어노테이션을 붙여 테이블의 Primary Key와 연결 시켜줘야한다.

이 때, 컬럼 명을 따로 지정하지 않으면, 관례에 따라 매핑되는 테이블 컬럼명은 camelCase로 작성된 필드명을 snake_case로 바뀐 테이블 컬럼을 찾아서 매핑시켜준다. ex) memberId -> member_id , orderItemId -> order_item_id 

 

@Column 어노테이션을 활용하여 테이블의 pk 컬럼을 따로 지정할 수도 있다.

public class  Member {
    @Id  @Column(name = "member_id") // 컬럼명 따로 지정
    private Long id;
    
    }

 

 

이렇게 @Id로 식별자필드와 테이블의 PK를 매핑만 시켜놓으면, 식별자로 사용될 값을 일일히 수동으로 넣어줘야 하는 불편함이 있는데, @GeneratedValue 를 사용하면 이를 해결할 수 있다.

 

@GeneratedValue 어노테이션을 사용하면 식별자 값을 자동 생성 시켜줄 수 있다.

@GeneratedValue에는 3가지 전략이 있고, JPA에게 전략 선택을 위임하는 옵션인 AUTO 옵션을 포함해, 총 4가지 옵션이 존재한다.

 

1. GenerationType.AUTO 옵션.(자동으로 IDENTITY, SEQUENCE, TABLE 中 택 1)

 

 hibernate.dialect에 설정된 DB 방언 종류에 따라, 하이버네이트가 자동으로 전략을 선택하게끔 위임한다.

@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>

*주의 :하이버네이트를 무조건 믿어선 안된다! Mysql의 경우 Auto로 설정하면 당연히 Identity전략을 취할 것이라 생각하고 생략하거나, 추후 DBMS 종류 변경을 고려해 그냥 Auto로 사용하는 경우가 있는데, 버전에 따라 선택되는 전략이 달라질 수 있으므로, 직접 DBMS에 맞는 전략을 지정해주도록 한다.

Mysql일 때 GenerationType.AUTO 에서의 전략 선택 알고리즘(빨간 화살표 흐름)

 

Hibernate 5부터 MySQL에서의 GenerationType.AUTO는 IDENTITY가 아닌 TABLE을 기본 시퀀스 전략으로 가져간다.

참고 : https://jojoldu.tistory.com/295

 

2. IDENTITY 전략

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

- 기본 키 생성을 데이터베이스에 위임한다. 

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

- IDENTITY 전략은, em.persist()로 객체를 영속화 시키는 시점에 곧바로 insert 쿼리가 DB로 전송되고, 거기서 반환받은 식별자 값을 가지고 1차 캐시에 엔티티를 등록시켜 관리한다.

 

JPA는 보통의 경우에, 트랜잭션이 commit 되는 시점에 쓰기 지연 저장소에 모아놓은 SQL을 한 번에 DB로 전송하며 실행한다. 이렇게 해야 어플리케이션과 DB 사이에 네트워크를 오가는 횟수가 줄어들고 성능면에서 이득을 볼 수 있기 때문이다.

 

하지만 IDENTITY전략은 DB에 기본키 생성을 위임하므로, Mysql의 경우 AUTO_INCREMENT를 활용하여 생성하는데,

이 때, JPA 입장에선 DB에 INSERT SQL를 실행하기 전엔 도저히 AUTO_INCREMENT되는 값을 알 수 없으므로, persist() 시점에 insert 쿼리가 실행되는 것이다. (영속성 컨텍스트로 엔티티를 관리하려면 1차 캐시에 Id값을 key 값으로 들고 있어야 하기 때문에)

 

아래 그림에서 1차 캐시의 Key, Value 값 구조와 쓰기 지연 SQL 저장소와 flush()가 트랜잭션 commit 직전에 이루어지는 순서를 보면 이해가 쉬울 것이다.

 

 

 

3. SEQUENCE 전략

@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; 
 
 }

- DB의 시퀀스를 활용하여 Id값을 증가시킨다.

- 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트(예: 오라클 시퀀스)

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

 

sequenceName 으로 시퀀스를 분리하여 지정할 수 있고, allocationSize로 한 번에 사용할 시퀀스 덩어리 사이즈를 정해서 최적화 할 수 있다.

 

4. TABLE 전략

 

@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; 
 }

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

- 모든 데이터베이스에 적용 가능하나, 성능적인 손해가 있어서 잘 쓰지 않는다.

 

 

결론 : @GeneratedValue 사용시, 반드시  @GeneratedValue(strategy = GenerationType.전략명) 와 같은 형태로 GenerationType 명시해줘야 한다! 기본값인 AUTO는 못미덥다. 사용하지 말자!

 

JPA와 Mysql 사용시, @GeneratedValue의 GenerationType은 IDENTITY로 설정해서 쓰자!