GenerationType
test 코드를 실행하면서 생긴 문제들이 GenerationType과 관련되어있어 이를 정리해봤다.
GenerationType 에는 AUTO, IDENTITY, SEQUENCE, TABLE 이 있다.
AUTO
id 값을 null로 하면 DB가 알아서 AUTO_INCREMENT 해준다.
기본 설정 값으로 각 데이터베이스에 따라 기본키를 자동으로 생성한다.
insert 쿼리 전에 select, update 쿼리가 실행된다.
기본 키 제약 조건
- null이 아니다.
- 유일하다.
- 변하면 안된다
방언에 따라 TABLE
, IDENTITY
, SEQUENCE
3개 중에 하나가 자동으로 지정된다.
AUTO로 설정 시
- MySQL :
IDENTITY
- Hibernate 5.0부터는 MySQL의
AUTO
는IDENTITY
가 아닌TABLE
을 기본 시퀀스 전략으로 선택한다. - 관련 글
- Hibernate 5.0부터는 MySQL의
- H2 :
SEQUENCE
정리
이 전에 내가 Comment와 Registry를 AUTO
로 설정했을 때
내 버전은 5.0 이상인걸로 확인했으나 HIBERANTE SEQUENCE TABLE이 안나왔었다.
그래서 해당 글이 맞는지 확신을 못했었는데 블로그 글을 보고 왜 안나왔는지 이유를 알 수 있었다.
먼저 정리를 하자면 Spring Boot는 Hibernate의 id 생성 전략을 그대로 따라갈지 말지를 결정하는
useNewIdGeneratorMappings
설정이 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// HibernateProperties.java(2.x)
/**
* Whether to use Hibernate's newer IdentifierGenerator for AUTO, TABLE and SEQUENCE.
* This is actually a shortcut for the "hibernate.id.new_generator_mappings" property.
* When not specified will default to "true".
*/
private Boolean useNewIdGeneratorMappings;
private void applyNewIdGeneratorMappings(Map<String, Object> result) {
if (this.useNewIdGeneratorMappings != null) {
result.put(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, this.useNewIdGeneratorMappings.toString());
}
else if (!result.containsKey(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS)) {
result.put(AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true");
}
}
Spring Boot 1.5에선 기본값이 false, Spring Boot 2.0부터는 true이다.
Spring Boot 2.0 Migration Guide
Hibernate 5.0부터 MySQL의 AUTO는 IDENTITY
가 아닌 TABLE
을 기본 시퀀스 전략으로 선택된다.
Spring Boot 1.5에선 Hibernate 5를 쓰더라도 AUTO를 따라가지 않기 때문에 IDENTITY
가 선택되고,
Spring Boot 2.0에선 true이므로 Hibernate 5를 그대로 따라가기 때문에 TABLE
이 선택된다.
만약 이를 해결하고 싶다면 2가지 방법이 있다.
이 중 나는 1번을 적용하고 있어서 TABLE
이 아닌 IDENTITY
가 되었던 것이다.
1. application.properties에서 아래와 같이 false로 설정한다.
1
2
# do not share id
spring.jpa.hibernate.use-new-id-generator-mappings= false
2. @GeneratedValue의 전략을 GenerationType.IDENTITY
로 지정한다
IDENTITY
기본키 생성을 db에게 위임하는 방식으로 id값을 따로 할당하지 않아도
db가 자동으로 AUTO_INCREMENT를 하여 기본키를 생성해준다.
id 값을 null로 하면 DB가 알아서 AUTO_INCREMENT 해준다.
JPA는 보통 영속성 컨텍스트에서 객체를 관리하다가 트랜잭션 커밋 시점에 INSERT 쿼리문을 실행한다.
하지만 IDENTITY 전략에서는 EntityManager.persist()
를 하는 시점에 Insert SQL을 실행하여 DB에서 식별자를 조회해온다.
그 이유는 영속성 컨텍스트는 1차 캐시에 PK와 객체를 가지고 관리를 하는데 기본키를 데이터베이스에게 위임했기 때문에
EntityManager.persist()
호출 하더라도 DB에 값을 넣기 전까지 기본키를 모르고 있기 때문에 관리가 되지 않기 때문이다.
AUTO_INCREMENT는 DB에 INSERT 쿼리문을 실행 한 이후에 ID값을 알 수 있다.
영속성 관리 시점에서 1차캐시에 @Id값을 알 수 없다는 말이된다. (IDENTITY 전략은 AUTO_INCREMENT로 동작하기 때문)
따라서 IDENTITY 에서는 EntityManager.persist()
를 하는 시점에 insert 쿼리를 실행해
DB에서 식별자를 조회하여 영속성 컨텍스트 1차 캐시에 값을 넣어주기 때문에 관리가 가능해진다.
그렇기에 IDENTITY 에서는 지연쓰기가 제한된다 . (하지만 크게 성능 하락이 있거나 하지는 않다.)
Sequence
DB Sequence Object를 사용할 때 쓰인다.
Current value를 서로 공유한다.(시퀀스 값을 공유해서 사용)
- 공용 시퀀스 테이블을 두고 모든 테이블의 id 시퀀스를 한 테이블에서 관리한다.
Table
키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략
Hibernate_Sequence 생성
모든 데이터 베이스에서 사용할 수 있지만, 성능이 떨어진다.
문제 상황
IDENTITY(EntityNotFoundException)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
void commentSave() {
Registry registry = new Registry();
registry.setIdx(3L); // 👈🏻
registry.setNickname("coco");
registry.setTitle("안녕하세요");
registry.setMain("hi");
registryRepository.save(registry);
Comment comment = new Comment();
comment.setComment("❤️🧡💛💚💙💜🤎🖤");
comment.setNickname("우헤헤");
comment.setRegistryId(5L);
comment.setRegistryNickname("pop");
comment.setRegistry(registry);
commentRepository.save(comment);
Comment savedComment = commentRepository.findById(1L).get();
Registry savedRegistry = savedComment.getRegistry();
Assertions.assertThat("coco").isEqualTo(savedRegistry.getNickname());
Assertions.assertThat("❤️🧡💛💚💙💜🤎🖤").isEqualTo(savedComment.getComment());
}
nested exception is javax.persistence.EntityNotFoundException 에러가 떴다.
IDENTITY
로 설정 후 3L로 저장했을 때 3L을 갖고오지 못하는 이유는 auto_increment로 인해 1L로 저장이 된다.
🐣 해당 에러는 데이터를 저장할 때 @OneToOne, @OneToMany.. 등의 annotation이 선언되어 있을 경우에
매핑된 id값이 0인 경우에 오류가 발생하는 경우가 있다. id값을 null 처리를 하게 되면 조회 할 수 있다.
→ idx 값 설정을 생략하라는 말
AUTO(값을 제대로 못가져 오는 상황)
pk를 1L로 저장을 하고 꺼내올 때 1L로 꺼내오려고 하면 에러가 뜨고 계속 2L로 가져오는 상황이 나타났다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@DataJpaTest
public class CommentandRegistryTest {
@Test
void commentSave() {
Registry registry = new Registry();
registry.setIdx(1L);
registry.setNickname("coco");
registry.setTitle("안녕하세요");
registry.setMain("hi");
registryRepository.save(registry);
Comment comment = new Comment();
comment.setIdx(1L);
comment.setComment("❤️🧡💛💚💙💜🤎🖤");
comment.setNickname("우헤헤");
comment.setRegistryId(5L);
comment.setRegistryNickname("pop");
comment.setRegistry(registry);
commentRepository.save(comment);
System.out.println("comment.getComment() : " + comment.getComment());
Comment savedComment = commentRepository.findById(2L).get(); // ←
Registry savedRegistry = savedComment.getRegistry();
System.out.println("savedComment : " + savedComment);
System.out.println("savedRegistry : " + savedRegistry);
System.out.println("(savedRegistry.getComments() : " +savedRegistry.getComments());
}
}
Comment의 GenerationType을 AUTO
로 설정했었는데 IDENTITY
로 바꾸면서 정상적으로 1L로 값을 가져왔다.
여기서 @DataJpaTest를 사용했기 때문에(H2) AUTO
는 SEQUENCE
이다.
그러므로 Current value로 값을 공유해서 먼저 저장한 것이 l, 이후에 저장한 것이 2로 나오게 된 것이다.
test 해보기
왜 AUTO로 설정하면 값이 1L이 아닌 2L로 저장이 될까???
Registry와 Comment 설정을 AUTO(@GeneratedValue(strategy = GenerationType.AUTO)
)로 하고
아래 코드를 작성한 후 출력시켰다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
void autoTest() {
Registry registry1 = new Registry();
registry1.setNickname("coco");
registry1.setTitle("안녕하세요");
registry1.setMain("hi");
Comment comment1 = new Comment();
comment1.setComment("❤️🧡💛💚💙💜🤎🖤");
comment1.setNickname("우헤헤");
comment1.setRegistryId(5L);
comment1.setRegistryNickname("pop");
comment1.setRegistry(registry1);
Registry savedRegistry1 = registryRepository.save(registry1);
Comment savedComment1 = commentRepository.save(comment1);
System.out.printf("%s idx : %d\n", "comment", savedComment1.getIdx());
System.out.printf("%s idx : %d\n", "registry", savedRegistry1.getIdx());
}
둘다 1로 나올 것만 같았지만 결과는 1과 2로 나왔다.
Current value로 값을 공유해서 먼저 저장한 것이 l, 이후에 저장한 것이 2로 저장한것이 아닐까 라는 생각으로
Comment와 Registry를 하나씩 더 추가해봤다.
저장된 순서대로 값이 출력되고 있었고 idx 값을 공유하고 있다고 판단할 수 있었다.
H2 콘솔로 확인을 해봤다.
H2
application
test
이번엔 Comment와 Registry를 SEQUENCE
로 바꾸고 application을 실행해봤다.
h2로 실행해보니 HIBERNATE_SEQUENCE가 생겼다. 그런데 2개를 변경했는데 HIBERNATE_SEQUENCE는 하나만 생긴 것을 볼 수 있다.
*HIBERNATE_SEQUENCE 아래 SYSTEM_SEQUENCE는 기존에 있던 다른 클래스로 인해 생긴 것
여기서 몇 가지를 파악할 수 있었다. AUTO
로 설정하면
- test 코드에서 Current value를 서로 공유하는 것을 볼 수 있었다.
- H2는 auto가
SEQUENCE
이기 때문에 Current value를 공유하는 것을 볼 수 있다.
- H2는 auto가
- application으로 실행하면 Syestem_Sequence가 떴고 test에서는 table hibernate_sequences가 생성되었다.
이번에는 TABLE
로 변경해봤다.
MySQL
이번엔 MySQL로 Test 해봤다.
MySQL도 TEST 해본 결과 H2에서는 뜨던 SYSTEM_SEQUENCE가 뜨지 않고
SEQUENCE
로 설정하거나 TABLE
로 설정했을 시 HIBERNATE SEQUENCE가 뜨는 것을 볼 수 있었다.
MySQL에는 시퀀스 기능이 없다고한다. 그래서 sequence전략 사용시 hibernate_sequence 테이블을 생성한다.
이를 통해서 SEQUENCE
와 TABLE
의 차이는 HIBERANTE SEQUENCE TABLE을 만들지만
SEQUENCE는 Current value를 공유하고 table은 공유하지 않는다는 차이점도 파악할 수 있었다.
정리
EntityManager.persist()
를 하는 시점에 기본키를 알면 AUTO
, 모르면 IDENTITY
어떤 db냐에 따라서 AUTO가 바뀐다.
- 버전 차이에 따라서 AUTO 기본 값이 다르다.
MySql, H2 DB인 경우
SEQUENCE
일 때 Current value를 공유한다.
GenerationType을 잘 모르는 상황에서 AUTO
로 작성했을 때 어떻게 돌아가는지 모르는 경우 위험하다.
따라서 직접 정해서 작성해주는 것이 좋은데 MySQL에서는 SEQUENCE
기능을 제공하지 않고 있다.
사실상 TABLE
과 IDENTITY
인데 TABLE을 생성하기 보다는 IDENTITY
로 하는게 낫다고 생각한다.
그래서 AUTO
쓸 바에는 IDENTITY
쓴다.😂
출처
@GeneratedValue 전략
(JPA) JPA @Id GenerationType.AUTO, IDENTITY 차이
[JPA] @GeneratedValue AUTO와 IDENTITY 차이점
[JPA] 기본키(PK) 매핑 방법 및 생성 전략
javax.persistence.EntityNotFoundException: Unable to find … with id 0 에러
엔티티 매핑
Spring Boot Data JPA 2.0 에서 id Auto_increment 문제 해결
Hibernate 기본키 전략은 무엇을 사용해야 할까?