【问题标题】:Hibernate not recognizing sequence increment correctlyHibernate 无法正确识别序列增量
【发布时间】:2021-04-16 10:18:46
【问题描述】:

我们使用带有 Hibernate 5.4.8.Final 的 Spring Boot 2.2.1,方言 org.hibernate.dialect.Oracle12cDialect,针对带有 Open JDK 11 的 Oracle 19.3.0.0 数据库。

仅对某些开发人员而言,Hibernate 随机无法初始化并出现此异常:

org.hibernate.MappingException: The increment size of the [SEQ_THE_SEQUENCE] sequence is set to [10] in the entity mapping while the associated database sequence increment size is [1].
    at org.hibernate.id.enhanced.SequenceStyleGenerator.configure(SequenceStyleGenerator.java:257)
    at org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory.createIdentifierGenerator(DefaultIdentifierGeneratorFactory.java:118)
    ... 42 common frames omitted
Wrapped by: org.hibernate.MappingException: Could not instantiate id generator [entity-name=com.[...].TheEntity]
    at org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory.createIdentifierGenerator(DefaultIdentifierGeneratorFactory.java:124)
    at org.hibernate.mapping.SimpleValue.createIdentifierGenerator(SimpleValue.java:344)
    at org.hibernate.internal.SessionFactoryImpl.lambda$new$1(SessionFactoryImpl.java:292)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:177)
    at java.base/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1675)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
    at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:291)
    at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:462)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1249)
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:391)
    ... 25 common frames omitted
Wrapped by: javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not instantiate id generator [entity-name=com.[...].TheEntity]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:403)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:378)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1862)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1799)
    ... 21 common frames omitted
Wrapped by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Could not instantiate id generator [entity-name=com.[....]TheEntity]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1803)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1108)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:868)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
    at ....main(....java:28)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)

序列由以下语句创建(我们使用的是flyway,所以在所有环境中都是一样的):

create sequence SEQ_THE_SEQUENCE
  start with 1
  increment by 10;

具有抽象父级的实体:

@MappedSuperclass
public abstract class AbstractParentEntity implements JpaBaseEntity {

    public static final String SEQ_GENERATOR_NAME = "SEQ_GENERAL";

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQ_GENERATOR_NAME)
    @Column(name = "[ID]", unique = true, nullable = false, insertable = false, updatable = false)
    protected Long id;
...
}
@Entity
@Table(name = "[THE_ENTITY]")
@SequenceGenerator(name = AbstractParentEntity.SEQ_GENERATOR_NAME, sequenceName = "SEQ_THE_SEQUENCE", allocationSize = 10)
public class TheEntity extends AbstractParentEntity {
...
}

我们的应用使用的是Pooled Sequence generator,而不是hilo,它是在spring配置中配置的:

spring.jpa.properties.hibernate.id.new_generator_mappings: true

我知道https://hibernate.atlassian.net/browse/HHH-13783,但我认为它不适合我们的情况,因为它明确只会影响 hilo。

我已经对数据库中的序列进行了两次和三次检查,但它声明“以 10 为增量”,并且与在其中工作的所有其他环境中相同。我已经清理并重建了应用程序。我已经多次删除并重新创建了数据库并重新启动了 Oracle 数据库,因为我已经没有东西可以尝试了。我还能做些什么来解决这个问题?

编辑 1:

调试到SequenceStyleGenerator后,我可以解释这种行为。

这个 Hibernate 类中的方法getSequenceIncrementValue 尝试从数据库中读取增量值:

private Long getSequenceIncrementValue(JdbcEnvironment jdbcEnvironment, String sequenceName) {
        return jdbcEnvironment.getExtractedDatabaseMetaData().getSequenceInformationList()
                .stream()
                .filter(
                    sequenceInformation -> {
                        Identifier catalog = sequenceInformation.getSequenceName().getCatalogName();
                        Identifier schema = sequenceInformation.getSequenceName().getSchemaName();
                        return sequenceName.equalsIgnoreCase( sequenceInformation.getSequenceName().getSequenceName().getText() ) &&
                                ( catalog == null || catalog.equals( jdbcEnvironment.getCurrentCatalog() ) ) &&
                                ( schema == null || schema.equals( jdbcEnvironment.getCurrentSchema() ) );
                    }
                )
                .map( SequenceInformation::getIncrementValue )
                .filter( Objects::nonNull )
                .findFirst()
                .orElse( null );
    }

...getSequenceInformationList() 的结果包含 两个 名为 SEQ_THE_SEQUENCE 的条目。一个来自应用程序模式,一个来自数据库中的另一个模式。该序列也存在那里,并且增量为 1。由于从 sequenceInformation 对象派生的变量 schema 对这两个条目都是空的,因此 Hibernate 处理第一个 - 那个,那是错误的架构。

现在的问题是,为什么缺少这个元数据。

【问题讨论】:

  • 你使用什么hibernate方言?
  • org.hibernate.dialect.Oracle12cDialect,已将其添加到帖子中。

标签: java oracle hibernate


【解决方案1】:

如果与 Hibernate 一起使用的 DB 用户可以访问数据库中的多个模式,它将检索有关所有序列的信息,因为使用了查询 select * from all_sequences。如果同名的序列出现在不同的模式中,第一个序列将随机与 Hibernate 定义进行比较。

不幸的是,Oracle 似乎无法获得属于序列的模式,正如SequenceInformationExtractorOracleDatabaseImplshows 中的实现:

    @Override
    protected String sequenceSchemaColumn() {
        return null;
    }

这仅在本地环境(也称为“我的机器”)中发生在我们身上,因为我们在那里有一个可以访问多个模式的用户。这只发生在一些开发人员身上,因为集成测试数据库架构有时处于不同应用程序版本的状态,然后发生了不匹配。

我们需要更多地限制用户。

【讨论】:

  • 您可以覆盖 Oracle12cDialect 中的 getQuerySequencesString() 方法来纠正此行为。见this
猜你喜欢
  • 1970-01-01
  • 2014-08-07
  • 1970-01-01
  • 2022-11-07
  • 1970-01-01
  • 1970-01-01
  • 2013-07-06
  • 2021-05-13
  • 1970-01-01
相关资源
最近更新 更多