Home Generationtype
Post
Cancel

Generationtype

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의 AUTOIDENTITY가 아닌 TABLE을 기본 시퀀스 전략으로 선택한다.
    • 관련 글
  • H2 : SEQUENCE

image




정리

이 전에 내가 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이다.

image

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());

    }
}

image



Comment의 GenerationType을 AUTO로 설정했었는데 IDENTITY로 바꾸면서 정상적으로 1L로 값을 가져왔다.


여기서 @DataJpaTest를 사용했기 때문에(H2) AUTOSEQUENCE이다.

그러므로 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());
    }

image

둘다 1로 나올 것만 같았지만 결과는 1과 2로 나왔다.

Current value로 값을 공유해서 먼저 저장한 것이 l, 이후에 저장한 것이 2로 저장한것이 아닐까 라는 생각으로

Comment와 Registry를 하나씩 더 추가해봤다.



image

저장된 순서대로 값이 출력되고 있었고 idx 값을 공유하고 있다고 판단할 수 있었다.



H2 콘솔로 확인을 해봤다.



H2

application

image


test

image



이번엔 Comment와 Registry를 SEQUENCE로 바꾸고 application을 실행해봤다.

image


h2로 실행해보니 HIBERNATE_SEQUENCE가 생겼다. 그런데 2개를 변경했는데 HIBERNATE_SEQUENCE는 하나만 생긴 것을 볼 수 있다.

*HIBERNATE_SEQUENCE 아래 SYSTEM_SEQUENCE는 기존에 있던 다른 클래스로 인해 생긴 것



여기서 몇 가지를 파악할 수 있었다. AUTO로 설정하면

  • test 코드에서 Current value를 서로 공유하는 것을 볼 수 있었다.
    • H2는 auto가 SEQUENCE이기 때문에 Current value를 공유하는 것을 볼 수 있다.
  • application으로 실행하면 Syestem_Sequence가 떴고 test에서는 table hibernate_sequences가 생성되었다.



이번에는 TABLE로 변경해봤다.

image




MySQL

이번엔 MySQL로 Test 해봤다.

MySQL도 TEST 해본 결과 H2에서는 뜨던 SYSTEM_SEQUENCE가 뜨지 않고
SEQUENCE로 설정하거나 TABLE로 설정했을 시 HIBERNATE SEQUENCE가 뜨는 것을 볼 수 있었다.

image


MySQL에는 시퀀스 기능이 없다고한다. 그래서 sequence전략 사용시 hibernate_sequence 테이블을 생성한다.

이를 통해서 SEQUENCETABLE의 차이는 HIBERANTE SEQUENCE TABLE을 만들지만
SEQUENCE는 Current value를 공유하고 table은 공유하지 않는다는 차이점도 파악할 수 있었다.




정리

EntityManager.persist()를 하는 시점에 기본키를 알면 AUTO, 모르면 IDENTITY


  • 어떤 db냐에 따라서 AUTO가 바뀐다.

    • 버전 차이에 따라서 AUTO 기본 값이 다르다.
  • MySql, H2 DB인 경우 SEQUENCE일 때 Current value를 공유한다.



GenerationType을 잘 모르는 상황에서 AUTO로 작성했을 때 어떻게 돌아가는지 모르는 경우 위험하다.

따라서 직접 정해서 작성해주는 것이 좋은데 MySQL에서는 SEQUENCE 기능을 제공하지 않고 있다.

사실상 TABLEIDENTITY인데 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 기본키 전략은 무엇을 사용해야 할까?

This post is licensed under CC BY 4.0 by the author.