【问题标题】:Hibernate could not fetch the SequenceInformation from the databaseHibernate 无法从数据库中获取 SequenceInformation
【发布时间】:2020-02-22 11:39:31
【问题描述】:

我最近将应用程序中的休眠更新为 5.4.4.Final。现在,我在部署过程中遇到了以下异常。

ERROR [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentImpl|[STANDBY] ExecuteThread: '5' for queue: 'weblogic.kernel.Default (self-tuning)']
Could not fetch the SequenceInformation from the database
java.sql.SQLException: Numeric Overflow
        at oracle.jdbc.driver.NumberCommonAccessor.throwOverflow(NumberCommonAccessor.java:4136)
        at oracle.jdbc.driver.NumberCommonAccessor.getLong(NumberCommonAccessor.java:634)
        at oracle.jdbc.driver.GeneratedStatement.getLong(GeneratedStatement.java:206)
        at oracle.jdbc.driver.GeneratedScrollableResultSet.getLong(GeneratedScrollableResultSet.java:259)
        at oracle.jdbc.driver.GeneratedResultSet.getLong(GeneratedResultSet.java:558)
        at weblogic.jdbc.wrapper.ResultSet_oracle_jdbc_driver_ForwardOnlyResultSet.getLong(Unknown Source)
        at org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl.resultSetMaxValue(SequenceInformationExtractorLegacyImpl.java:139)
        at org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl.extractMetadata(SequenceInformationExtractorLegacyImpl.java:61)
        at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentImpl.sequenceInformationList(JdbcEnvironmentImpl.java:403)
        at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentImpl.<init>(JdbcEnvironmentImpl.java:268)
        at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:114)
        at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:35)
        at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:101)
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263)
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:237)
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214)
        at org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory.injectServices(DefaultIdentifierGeneratorFactory.java:152)
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:286)
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:243)
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214)
        at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:175)
        at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:118)
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:900)
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:931)
        at org.hibernate.jpa.HibernatePersistenceProvider.createContainerEntityManagerFactory(HibernatePersistenceProvider.java:141)
        at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:343)
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:318)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1633)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1570)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
        at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:956)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:747)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
        at com.sternkn.app.services.web.AppContextLoaderListener.<clinit>(AppContextLoaderListener.java:30)

我使用以下persistence.xml。

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
   version="2.2">

   <persistence-unit name="appPersistenceUnit" transaction-type="RESOURCE_LOCAL">

      <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle12cDialect" />
            <property name="hibernate.id.new_generator_mappings" value="true"/>

            <property name="hibernate.cache.use_second_level_cache" value = "true"/>
            <property name="hibernate.cache.use_query_cache" value="false" />
            <property name="hibernate.cache.region.factory_class" value="ehcache"/>
            <property name="hibernate.cache.ehcache.missing_cache_strategy" value="create" />
            <property name="hibernate.cache.region_prefix" value="app_cache" />
            <property name="net.sf.ehcache.configurationResourceName" value="/META-INF/app-ehcache.xml" />
            <property name="hibernate.bytecode.provider" value="bytebuddy" />
        </properties>
    </persistence-unit>
</persistence>

经过进一步调查,我发现根本原因如下:hibernate使用SequenceInformation接口进行序列元数据操作

public interface SequenceInformation {
  Long getMinValue();
  Long getMaxValue();
  Long getIncrementValue();
  ...
}

但是,我的应用使用如下序列:

SQL> CREATE SEQUENCE SEQ_TEST START WITH 1 INCREMENT BY 1 NOCYCLE;
SQL> select MIN_VALUE, MAX_VALUE, INCREMENT_BY
from USER_SEQUENCES
where SEQUENCE_NAME = 'SEQ_TEST';

MIN_VALUE MAX_VALUE                    INCREMENT_BY
--------- ---------------------------- ------------
1         9999999999999999999999999999 1

Long.MAX_VALUE 等于 9223372036854775807,因此我得到了数值溢出异常。

所以,我的问题:

  • 这是休眠中的错误吗?
  • 最好的解决方法是什么?

现在我看到了以下几种方式:

  1. 修复序列声明。 就我而言,这可能会很成问题。而且,顺便说一句,hibernate 尝试读取所有序列的元数据,而不仅仅是在我的应用程序中使用的元数据,这看起来很奇怪。
  2. 创建将扩展 Oracle12cDialect 并覆盖 getQuerySequencesString() 和/或 getSequenceInformationExtractor() 的自定义方言。
public class Oracle8iDialect extends Dialect {
  ...
  public String getQuerySequencesString() {
    return "select * from all_sequences";
  }

  public SequenceInformationExtractor getSequenceInformationExtractor() {
    return SequenceInformationExtractorOracleDatabaseImpl.INSTANCE;
  }
}

我可以将 SequenceInformationExtractor 切换到 SequenceInformationExtractorNoOpImpl.INSTANCE 并且休眠将不会读取序列元数据。这个决定会有什么影响? Hibernate 尝试通过 INCREMENT_BY 验证 @SequenceGenerator() 的 allocationSize。还有其他原因吗?

任何建议将不胜感激。

更新:这是HHH-13694

【问题讨论】:

    标签: java spring oracle hibernate


    【解决方案1】:

    最后,我想出了以下解决方案:

    1. 创建一个扩展SequenceInformationExtractorOracleDatabaseImpl的序列信息提取器:
    public class AppSequenceInformationExtractor extends SequenceInformationExtractorOracleDatabaseImpl 
    {
       /**
        * Singleton access
        */
       public static final AppSequenceInformationExtractor INSTANCE = new AppSequenceInformationExtractor();
       
       @Override
       protected Long resultSetMinValue(ResultSet resultSet) throws SQLException {
          return resultSet.getBigDecimal(super.sequenceMinValueColumn()).longValue();
       }
    }
    

    是的,我知道我们可能会丢失有关此 BigDecimal 值的整体大小和精度的信息,并返回带有相反符号的结果。但这并不重要,因为this Steve Ebersole 对来自SequenceInformation 接口的Long getMinValue()Long getMaxValue() 方法的评论:

    我实际上很想从SequenceInformation 中删除这两种方法。我们从不以任何有意义的方式使用它们。 或者将这 2 个方法的返回类型从 Long 更改为 BigInteger - 它可能是 BigDecimal,但该值隐含一个整数(在整个数感)。

    我想此时在游戏中做这些都为时已晚,所以像你的改变是好的 - 就像我说的那样,我们从不使用这些值。 我们绝对应该弃用这两种 IMO 方法。

    所以,这个技巧只允许用最少的笨拙的额外编码来避免异常。

    1. 创建一个扩展 Oracle12cDialect 的休眠方言:
    public class AppOracleDialect extends Oracle12cDialect
    {
       @Override
       public SequenceInformationExtractor getSequenceInformationExtractor() {
          return AppSequenceInformationExtractor.INSTANCE;
       }
       
       @Override
       public String getQuerySequencesString() {
          return "select * from user_sequences";
       }
    }
    
    1. 然后在persistence.xml中使用这个方言:
    <property name="hibernate.dialect" value="com.my.app.AppOracleDialect" />
    

    至于方法getQuerySequencesString() 覆盖和使用USER_SEQUENCES 而不是ALL_SEQUENCES 这是有争议的(参见HHH-13322HHH-14022)。但是,就我而言,USER_SEQUENCES 的用法更可取。

    【讨论】:

    【解决方案2】:

    对于 PostgreSQL,我使用这个类作为方言,直到 Hibernate 修复了这个问题:

    package com.societe.dialect;
    
    import org.hibernate.dialect.PostgreSQL95Dialect;
    import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
    import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
    
    public class PostgresqlCustomDialect extends PostgreSQL95Dialect {
    
        public String getQuerySequencesString() {
            return "select * from all_sequences";
        }
    
        public SequenceInformationExtractor getSequenceInformationExtractor() {
            return SequenceInformationExtractorNoOpImpl.INSTANCE;
        }
    }
    

    【讨论】:

      【解决方案3】:

      我会提出一个混合解决方案,将 @sternk 提出的内容与 hibernate 存储库中已经存在的内容作为 pull request 但尚未合并。

      一旦 BigDecimal 转换为 long,此处接受的答案会更改值的符号。

      为了查看接受的答案不起作用,您可以执行以下简单的单元测试:

        @Test
        public void testSignChange() {
      
          final BigDecimal minValue = BigDecimal.valueOf(Long.MIN_VALUE);
          final BigDecimal minValueMinusTen = minValue.subtract(BigDecimal.TEN);
          final long minValueMinusTenLong = minValueMinusTen.longValue();
          System.out.println("Min value as big decimal: " + minValueMinusTen);
          System.out.println("Min value as long: " + minValueMinusTenLong);
      
          final BigDecimal maxValue = BigDecimal.valueOf(Long.MAX_VALUE);
          final BigDecimal maxValuePlusTen = maxValue.add(BigDecimal.TEN);
          final long maxValuePlusTenLong = maxValuePlusTen.longValue();
          System.out.println("Max value as big decimal: " + maxValuePlusTen);
          System.out.println("Max value as long: " + maxValuePlusTenLong);
        }
      

      你会看到输出为

      Min value as big decimal: -9223372036854775818
      Min value as long: 9223372036854775798
      Max value as big decimal: 9223372036854775817
      Max value as long: -9223372036854775799
      

      因此,我会提出一个同时处理最小值和最大值的解决方案,如下所示:

      public class SequenceInformationExtractor extends SequenceInformationExtractorOracleDatabaseImpl {
      
        public static final SequenceInformationExtractor INSTANCE = new SequenceInformationExtractor();
      
        private static final BigDecimal LONG_MIN_VALUE_AS_DECIMAL = BigDecimal.valueOf(Long.MIN_VALUE);
      
        private static final BigDecimal LONG_MAX_VALUE_AS_DECIMAL = BigDecimal.valueOf(Long.MAX_VALUE);
      
        @Override
        protected String sequenceMaxValueColumn() {
          return "max_value";
        }
      
        @Override
        public Long resultSetMinValue(final ResultSet resultSet) throws SQLException {
          final BigDecimal asDecimal = resultSet.getBigDecimal(this.sequenceMinValueColumn());
          if (asDecimal.compareTo(SequenceInformationExtractor.LONG_MIN_VALUE_AS_DECIMAL) < 0) {
            return Long.MIN_VALUE;
          }
          return asDecimal.longValue();
        }
      
        @Override
        public Long resultSetMaxValue(final ResultSet resultSet) throws SQLException {
          final BigDecimal asDecimal = resultSet.getBigDecimal(this.sequenceMaxValueColumn());
          if (asDecimal.compareTo(SequenceInformationExtractor.LONG_MAX_VALUE_AS_DECIMAL) > 0) {
            return Long.MAX_VALUE;
          }
          return asDecimal.longValue();
        }
      }
      

      【讨论】:

      • 查看 Steve Ebersole 的 comment 对此拉取请求。实际上,Hibernate 并没有以任何有意义的方式使用 Long getMinValue()Long getMaxValue() 方法。所以,我的解决方案只是允许通过最少的尴尬额外编码来避免异常。
      【解决方案4】:

      在“application.properties”中添加(或更改,如果已经存在)如下方言属性。

      spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect

      【讨论】:

      • 将方言从 Oracle 更改为 MySQL 也应该假设更改数据库。我认为这对于具有重要应用程序的人来说是不可接受的。
      【解决方案5】:

      我解决了如下问题。为 Oracle12cDialect 创建了一个扩展。将列的最大值/最小值限制为 SQL

      package ru.mvawork.hibernate;
      
      import org.hibernate.dialect.Oracle12cDialect;
      
      @SuppressWarnings("unused")
      public class CustomOracleDialect extends Oracle12cDialect {
      
          @Override
          public String getQuerySequencesString() {
              return "select SEQUENCE_OWNER, SEQUENCE_NAME, greatest(MIN_VALUE,         -9223372036854775807) MIN_VALUE,\n"+
                      "Least(MAX_VALUE, 9223372036854775808) MAX_VALUE, INCREMENT_BY,     CYCLE_FLAG, ORDER_FLAG, CACHE_SIZE,\n"+
                      "Least(greatest(LAST_NUMBER, -9223372036854775807), 9223372036854775808) LAST_NUMBER,\n"+
                      "PARTITION_COUNT, SESSION_FLAG, KEEP_VALUE\n"+
                      "from all_sequences";
          }
      
      }
      

      在 application.properties 文件中引用了一个方言实现

      spring.jpa.properties.hibernate.dialect=ru.mvawork.hibernate.CustomOracleDialect
      

      您可以通过限制最小值和最大值来重新创建序列。就我而言,我做不到。我使用的主键的维度是 Number (12),它在从 -9223372036854775807 到 9223372036854775808 的范围限制内,并且有很大的余量

      【讨论】:

      • 感谢您的回答。其他读者可能希望从所选列中删除“PARTITION_COUNT”,因为它可能不存在于所有 Oracle 数据库中。见:stackoverflow.com/questions/50385126
      • 是的,它成功了,谢谢。就像我的情况一样,只需要为 oracle 删除“PARTITION_COUNT”。
      【解决方案6】:

      您简单地使用了序列的默认值MAX_VALUE,这对于 Java LONG 数据类型来说太高了。

      幸运的是,您可以随时将MAX_VALUE with ALTER SEQUENCE 重置为不会导致任何问题的较小数字。

      示例

      CREATE SEQUENCE SEQ_TEST START WITH 1 INCREMENT BY 1 NOCYCLE;
      
      
      select MAX_VALUE from ALL_SEQUENCES where SEQUENCE_NAME = 'SEQ_TEST';
      
       MAX_VALUE
      ----------
      9999999999999999999999999999
      
      
      ALTER SEQUENCE SEQ_TEST
        MAXVALUE 9223372036854775807;
      
      select MAX_VALUE from ALL_SEQUENCES where SEQUENCE_NAME = 'SEQ_TEST';
      
       MAX_VALUE
      ----------
      9223372036854775807
      

      顺便说一句

      hibernate 尝试读取所有序列的元数据,而不仅仅是在我的应用程序中使用的元数据,这看起来很奇怪。

      Hibernate 使用select * from all_sequences 作为Oracle 方言来获取序列信息。请注意,ALL_SEQUENCES 并不意味着 所有 现有 序列,但所有序列,您的 Hibernate 数据库用户(来自连接池的 DBUSER)被授予使用 - 当然是 绝对正确

      【讨论】:

      • Marmite Bomber,谢谢您的回复。但我担心在我的情况下它不会是可接受的解决方案,因为我们公司中的其他应用程序使用相同的数据库架构,并且由于这种更正可能会受到影响。
      • 是的,与其他应用程序共享 Hibernate 模式会增加风险。您是否至少使用了一个不同的连接用户作为您的竞争对手?
      • 每个应用都使用自己的weblogic数据源,但几乎所有这些数据源都使用同一个oracle DB用户。
      • this 设置好了,IMO 你证明你可以很好地管理潜在风险,所以你应该问 1) 序列的当前值是多少 (LAST_NUMBER ) 2) 每年的平均增幅是多少和 3) 你什么时候会达到Long.MAX_VALUE 的限制。如果是 100 多年以后,我会把解决方案推迟到那个时候;)
      • 我只是想知道为什么这个异常如此特别,虽然谷歌搜索 "Could not fetch the SequenceInformation from the database" "java.sql.SQLException: Numeric Overflow" 这只是现有的第二个匹配项。 Probalby 人使用现实的 非默认 MAX_VALUES 进行序列。
      猜你喜欢
      • 2021-09-24
      • 2018-05-14
      • 1970-01-01
      • 2017-09-21
      • 1970-01-01
      • 2020-08-30
      • 2018-05-28
      相关资源
      最近更新 更多