postgresql에서 jpa 자동 키 생성 오류

postgresql에서 jpa 자동 키 생성 오류

jpa에서 insert를 하는데 계속 오류메시지를 내뱉었다. 일단 구현체는 hibernate를 사용했는데 내가 무슨 관계를 잘못 맺은줄 알고 무려 이틀을 소모한뒤 동료들과 논의 했을때도 오류를 찾지 못했다.
내가 잘 모르고 사용했던것 같다 여기서 자동 키 맵핑 전략을 가지고 갔는데 이것이 문제였다.

아래 처럼 아주 간단한 클래스가 있다.

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

package io.github.sejoung.jpa.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@ToString
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class IronManAndCaptainAmericaAndHulkAndThor {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ironManAndCaptainAmericaAndHulkAndThorId;

private String name;

public IronManAndCaptainAmericaAndHulkAndThor(String name) {
this.name = name;
}
}


아래 처럼 Repository를 만들고

1
2
3
4
5
6
7
8
9
10
11
12

package io.github.sejoung.jpa.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import io.github.sejoung.jpa.domain.IronManAndCaptainAmericaAndHulkAndThor;

public interface IronManAndCaptainAmericaAndHulkAndThorRepository extends JpaRepository<IronManAndCaptainAmericaAndHulkAndThor,Long> {

}


1
2
3
4
5
6
7
8
9
10
11
12
13
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class IronManAndCaptainAmericaAndHulkAndThorRepositoryTest {

@Autowired
private IronManAndCaptainAmericaAndHulkAndThorRepository repository;

@DisplayName("에러남")
@Test
void error() {
var actual = repository.save(new IronManAndCaptainAmericaAndHulkAndThor("아이언맨"));
}
}

위처럼 save를 하는 순간 에러가 나온다.

1
2
3
4
5
6
7
8
9
10
11
12
13
insert 
into
iron_man_and_captain_america_and_hulk_and_thor
(name)
values
(?)
Hibernate:
select
currval('iron_man_and_captain_america_and_hulk_and_thor_iron_man_and_captain_america_and_hulk_and_thor_id_seq')
2021-09-16 23:12:46.254 WARN 17962 --- [ Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 42P01
2021-09-16 23:12:46.254 ERROR 17962 --- [ Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: relation "iron_man_and_captain_america_and_hulk_and_thor_iron_man_and_cap" does not exist
Position: 16

멘붕이 와서 구글링을 했지만 찾지를 못했다. 하이버네이트에서 자동생성키를 어떻게 생성하는지 알아야 된다.

postgresql에서는 자동생성키를 postgresql DATATYPE-SERIAL 타입을 가지고 생성을 한다.
문서에 보면 SERIAL 유형은 실제 유형이 아니라 고유 식별자 열을 생성하기 위한 편의상의 표현이라고 한다.

1
2
3
4
5
6
7
8
9
10
11

CREATE TABLE tablename (
colname SERIAL
);

CREATE SEQUENCE tablename_colname_seq AS integer;
CREATE TABLE tablename (
colname integer NOT NULL DEFAULT nextval('tablename_colname_seq')
);
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;

내부적으로는 위처럼 테이블을 생성할때 숫자형 타입을 만들면서 거기에 대입되는 SEQUENCE를 만들고 DEFAULT제약을 통해서 SEQUENCE를 맵핑하는 구조이다.

위처럼 자동적으로 맵핑이 되는데 여기서 문제가 생겼다.

저기위에 보면 SEQUENCE 생성규칙이 테이블명_컬럼명_seq로 생성이 되어야 된다. 그래서 위에 오류를 보면 규칙에 맞게 SEQUENCE를 호출을 한다. 근데 SEQUENCE를 찾지 못한다고 한다.

왜 찾지 못할까? 그럼 다시 만들어진 SEQUENCE명은 뭐지 찾아 보려고 했다.

postgresql System Catalog Information Functions 을 보면

pg_get_serial_sequence 라고 하는 function 이 존재한다. SERIAL 필드에 맵핑된 SEQUENCE명을 찾아 볼수있는 function 이다.

그래서 간단한 테스트 코드를 하나 만들었다.

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package io.github.sejoung.jpa.repository;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.InvalidDataAccessResourceUsageException;
import org.springframework.jdbc.core.JdbcTemplate;

import io.github.sejoung.jpa.annotation.SejoungDataJpaTest;
import io.github.sejoung.jpa.domain.IronManAndCaptainAmericaAndHulkAndThor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@SejoungDataJpaTest
class IronManAndCaptainAmericaAndHulkAndThorRepositoryTest {

@Autowired
private IronManAndCaptainAmericaAndHulkAndThorRepository repository;

@Autowired
private JdbcTemplate jdbcTemplate;

@DisplayName("에러남")
@Test
void error() {
var actual = assertThrows(
InvalidDataAccessResourceUsageException.class,
() -> repository.save(new IronManAndCaptainAmericaAndHulkAndThor("아이언맨")));
assertThat(actual.getMessage()).isEqualTo(
"could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet");
}

@DisplayName("에러 이유 확인")
@Test
void seqName() {
var actual = jdbcTemplate.queryForObject(
"select pg_get_serial_sequence('iron_man_and_captain_america_and_hulk_and_thor','iron_man_and_captain_america_and_hulk_and_thor_id')",
String.class);
log.debug("{}", actual);
assertThat(actual).isEqualTo(
"public.iron_man_and_captain_america_and_hulk_and_thor_iron_man_and_captain_america_and_hulk_and_thor_id_seq");
}
}

여기서 예상대로라면 오류가 나면 되지 않는다. 하지만 오류가 났다.

1
2
3
4
5
6
7
8
Expecting:
<"public.iron_man_and_captain_america__iron_man_and_captain_america__seq">
to be equal to:
<"public.iron_man_and_captain_america_and_hulk_and_thor_iron_man_and_captain_america_and_hulk_and_thor_id_seq">
but was not.
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)

시퀀스 명이 완전 틀리게 변했다.

이 내용은 postgresql 문서에도 없다 이것을 찾는데 이틀정도를 날렸다. 잘 공부해 둬야겠다.

참고자료