문제상황 및 파악
- 처음으로 프로젝트에 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.
- 기본키 매핑전략 학습 내용
- 자바 ORM 표준 JPA 프로그래밍
- allocation size 내용