본문 바로가기
🪲 bugs

Spring data JPA: Hibernate가 Sequence를 못찾아요

by iirin 2023. 9. 11.

문제상황 및 파악

  • 처음으로 프로젝트에 Postgresql 을 사용해보고 있습니다. MySQL과 가장 체감되는 달라지는 점이 기본키 매핑 전략 중 Sequence 를 지원한다는 점입니다.
  • 이번에 신나서 `Sequence` 전략을 사용해 봤는데, 왠걸 이렇게 에러가 납니다.
2023-09-11T14:39:29.956+09:00 ERROR 45454 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper   : ERROR: relation "member_seq" does not exist
  Position: 16
2023-09-11T14:39:29.967+09:00 ERROR 45454 --- [nio-8080-exec-1] p.l.b.g.e.GlobalExceptionHandler         : Exception occured: 

org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet [ERROR: relation "member_seq" does not exist
  Position: 16] [n/a]; SQL [n/a]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:256) ~[spring-orm-6.0.11.jar:6.0.11]
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:229) ~[spring-orm-6.0.11.jar:6.0.11]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550) ~[spring-orm-6.0.11.jar:6.0.11]
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-6.0.11.jar:6.0.11]
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) ~[spring-tx-6.0.11.jar:6.0.11]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152) ~[spring-tx-6.0.11.jar:6.0.11]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11]
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:164) ~[spring-data-jpa-3.1.3.jar:3.1.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-6.0.11.jar:6.0.11]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:244) ~[spring-aop-6.0.11.jar:6.0.11]
    at jdk.proxy2/jdk.proxy2.$Proxy146.save(Unknown Source) ~[na:na]
    at project.labelingtool.backend.app.member.adapter.out.persist.MemberRepository.join(MemberRepository.java:19) ~[main/:na]
    at project.labelingtool.backend.app.member.application.service.MemberService.join(MemberService.java:22) ~[main/:na]
    at project.labelingtool.backend.app.member.adapter.in.rest.MemberFacade.joinNewMember(MemberFacade.java:35) ~[main/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
// 이하생략

작성한 Entity 코드는 다음과 같습니다.

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@SecondaryTable(name = "member_account", pkJoinColumns = @PrimaryKeyJoinColumn(name = "member_id"))
@SecondaryTable(name = "member_hash", pkJoinColumns = @PrimaryKeyJoinColumn(name = "member_id"))
@SecondaryTable(name = "member_aggrement", pkJoinColumns = @PrimaryKeyJoinColumn(name = "member_id"))
public class Member extends UpdatedEntity {

    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(name = "member_id_seq") // 이부분이 제대로 작동하고 있지 않다는거죠
    private Long id;
    private String email;
    private String name;
    // 이하 기타 등등
  • sequence 이름을 member_id_seq로 지정해줬다고 생각했는데 기본 네이밍 전략을 따라 member_seq 만 줄창 찾고있는 hibernate....
  • 아래는 에러가 발생한 상황에서 실제로 실행된 쿼리입니다.
Hibernate: 
    select
        m1_0.id 
    from
        member m1_0 
    where
        m1_0.email=? fetch first ? rows only // 중복된 이메일을 찾는 쿼리
Hibernate: 
    select
        nextval('member_seq') // 시퀀스를 가져오려고 시도하는 중. 이게 아니라 member_id_seq란다...

해결방법 1

  • 다시 한번 학습했던 자료를 찾아보고 다음과 같이 코드를 수정했습니다.
  • @SequenceGenerator 를 통해 어플리케이션에서 id generator를 생성해주는 것입니다.
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@SequenceGenerator(name = "member_id_generator", // generator 추가
    sequenceName = "member_id_seq", // DB에서 찾아올 sequence이름
    initialValue = 1, // 시작하는 값
    allocationSize = 50) // 50개씩 불러와서 어플리케이션에서 사용합니다.
@SecondaryTable(name = "member_account", pkJoinColumns = @PrimaryKeyJoinColumn(name = "member_id"))
@SecondaryTable(name = "member_hash", pkJoinColumns = @PrimaryKeyJoinColumn(name = "member_id"))
@SecondaryTable(name = "member_aggrement", pkJoinColumns = @PrimaryKeyJoinColumn(name = "member_id"))
public class Member extends UpdatedEntity {

    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "member_id_generator") // generator 매핑합니다.
    private Long id;
    // 이하 기타등등
  • 이렇게 코드를 수정했을 때 아래와 같은 에러가 났습니다.
  • `@SequenceGenerator` 를 매핑하지 못하고 있음을 알 수 있습니다.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not instantiate id generator [entity-name=project.labelingtool.backend.app.member.domain.Member]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1770) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598) ~[spring-beans-6.0.11.jar:6.0.11]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.11.jar:6.0.11]

해결방법 2

  • 위 에러의 이유는 @SequenceGenerator 를 통해 매핑한 설정과 DB의 설정값이 다르기 때문에 일어난 오류였습니다.
  • allocationSize 를 DB와 똑같이 1로 맞춰줍니다.
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@SequenceGenerator(name = "mem_id_gen", sequenceName = "member_id_seq", initialValue = 1, allocationSize = 1) // allocationSize를 DB설정과 똑같이 해주어야 합니다.
@SecondaryTable(name = "member_account", pkJoinColumns = @PrimaryKeyJoinColumn(name = "member_id"))
@SecondaryTable(name = "member_hash", pkJoinColumns = @PrimaryKeyJoinColumn(name = "member_id"))
@SecondaryTable(name = "member_aggrement", pkJoinColumns = @PrimaryKeyJoinColumn(name = "member_id"))
public class Member extends UpdatedEntity {

    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "mem_id_gen")
    private Long id;
  • allocationSize가 좀 클수록 DB connection횟수가 줄어드는 이점이 있으므로 혹시 이를 원한다면 DB 설정도 같이 바꿔주어야 하겠습니다.
  • 이제 쿼리가 수정된 것을 확인할 수 있었습니다. 👏
Hibernate: 
    select
        nextval('member_id_seq')
Hibernate: 
    insert 
    into
        member
        (association,created_at,email,password,name,phonenumber,profile_url,role,updated_at,id) 
    values
        (?,?,?,?,?,?,?,?,?,?)

Refs.