【问题标题】:get next sequence value from database using hibernate使用休眠从数据库中获取下一个序列值
【发布时间】:2026-02-08 22:55:01
【问题描述】:

我有一个实体,它有一个必须从序列中设置的非 ID 字段。 目前,我获取序列的第一个值,将其存储在客户端,并根据该值进行计算。

但是,我正在寻找一种“更好”的方式来做到这一点。我已经实现了一种获取下一个序列值的方法:

public Long getNextKey()
{
    Query query = session.createSQLQuery( "select nextval('mySequence')" );
    Long key = ((BigInteger) query.uniqueResult()).longValue();
    return key;
}

但是,这种方式会显着降低性能(创建约 5000 个对象会减慢 3 倍 - 从 5740 毫秒到 13648 毫秒)。

我试图添加一个“假”实体:

@Entity
@SequenceGenerator(name = "sequence", sequenceName = "mySequence")
public class SequenceFetcher
{
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence")
    private long                      id;

    public long getId() {
        return id;
    }
}

但是这种方法也不起作用(所有返回的 Id 都是 0)。

有人可以告诉我如何有效地使用 Hibernate 获取下一个序列值吗?

编辑:经过调查,我发现调用Query query = session.createSQLQuery( "select nextval('mySequence')" ); 比使用@GeneratedValue 效率低得多- 因为Hibernate 以某种方式 设法减少访问@GeneratedValue 描述的序列时的提取次数。

例如,当我创建 70,000 个实体时(因此从同一个序列中提取了 70,000 个主键),我得到了我需要的一切。

然而,Hibernate 只发出 1404 select nextval ('local_key_sequence') 命令。注意:在数据库端,缓存设置为 1。

如果我尝试手动获取所有数据,则需要 70,000 次选择,因此性能差异很大。有谁知道 Hibernate 的内部功能,以及如何手动重现它?

【问题讨论】:

    标签: sql hibernate sequence nextval


    【解决方案1】:

    如果您使用的是 Oracle,请考虑为序列指定缓存大小。如果您经常以 5K 的批量创建对象,您可以将其设置为 1000 或 5000。我们为代理主键使用的序列做了这件事,并且惊讶于用 Java 手写的 ETL 过程的执行时间减少了一半。

    我无法将格式化的代码粘贴到评论中。这是序列 DDL:

    create sequence seq_mytable_sid 
    minvalue 1 
    maxvalue 999999999999999999999999999 
    increment by 1 
    start with 1 
    cache 1000 
    order  
    nocycle;
    

    【讨论】:

    • 这听起来很有趣。你能告诉我你用来设置批量创建(以及批量获取序列值)的注释吗?
    • @iliaden:我在序列 DDL 中指定了缓存大小。我无法将代码粘贴到评论中,所以我编辑了答案。
    • @Olaf: cache 1000 似乎是需要的字段,但是如何在 Hibernate 中映射它?
    • @iliaden:如果您之前的策略是有效的,只是缓慢,对序列定义的更改应该会产生魔力。在我们的例子中,我们不必更改应用程序中的任何内容,或进行新的构建。
    • @Olaf:我调查得更深了。并发现了一些黑色巫术魔法。查看已编辑的问题
    【解决方案2】:

    要获取新 id,您只需 flush 实体管理器即可。请参阅下面的getNext() 方法:

    @Entity
    @SequenceGenerator(name = "sequence", sequenceName = "mySequence")
    public class SequenceFetcher
    {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence")
        private long id;
    
        public long getId() {
            return id;
        }
    
        public static long getNext(EntityManager em) {
            SequenceFetcher sf = new SequenceFetcher();
            em.persist(sf);
            em.flush();
            return sf.getId();
        }
    }
    

    【讨论】:

    • 这就是问题所在 - 序列不适用于主键(不是 ID 字段)。虽然我会调查SequenceFetcher的使用
    • SequenceFether 是一种额外的类方法,但 hibernate 强制将其映射到 [不存在] 表。
    • "em.flush()" 给了我一个错误。我删除了它,它工作得很好。谢谢!
    【解决方案3】:

    我找到了解决办法:

    public class DefaultPostgresKeyServer
    {
        private Session session;
        private Iterator<BigInteger> iter;
        private long batchSize;
    
        public DefaultPostgresKeyServer (Session sess, long batchFetchSize)
        {
            this.session=sess;
            batchSize = batchFetchSize;
            iter = Collections.<BigInteger>emptyList().iterator();
        }
    
            @SuppressWarnings("unchecked")
            public Long getNextKey()
            {
                if ( ! iter.hasNext() )
                {
                    Query query = session.createSQLQuery( "SELECT nextval( 'mySchema.mySequence' ) FROM generate_series( 1, " + batchSize + " )" );
    
                    iter = (Iterator<BigInteger>) query.list().iterator();
                }
                return iter.next().longValue() ;
            }
    
    }
    

    【讨论】:

    • 请注意:因为该类表明这仅适用于 PostgreSQL,因为 SQL 语法是从 Oracle 引用的。
    【解决方案4】:

    这对我有用(特定于 Oracle,但使用 scalar 似乎是关键)

    Long getNext() {
        Query query = 
            session.createSQLQuery("select MYSEQ.nextval as num from dual")
                .addScalar("num", StandardBasicTypes.BIG_INTEGER);
    
        return ((BigInteger) query.uniqueResult()).longValue();
    }
    

    感谢这里的海报:springsource_forum

    【讨论】:

    • 请注意,您也可以使用 StandardBasicTypes.LONG 代替 StandardBasicTypes.BIG_INTEGER...
    • 显然您并不“需要”addScalar 部分(休眠将返回 Integer 或 BigInteger,具体取决于您的 DB 序列底层类型是什么),但因为它可能因 DB(和序列)而异),为什么不保证它总是返回一个长的,就像这样......
    【解决方案5】:

    POSTGRESQL

    String psqlAutoincrementQuery = "SELECT NEXTVAL(CONCAT(:psqlTableName, '_id_seq')) as id";
    
    Long psqlAutoincrement = (Long) YOUR_SESSION_OBJ.createSQLQuery(psqlAutoincrementQuery)
                                                          .addScalar("id", Hibernate.LONG)
                                                          .setParameter("psqlTableName", psqlTableName)
                                                          .uniqueResult();
    

    MYSQL

    String mysqlAutoincrementQuery = "SELECT AUTO_INCREMENT as id FROM information_schema.tables WHERE table_name = :mysqlTableName AND table_schema = DATABASE()";
    
    Long mysqlAutoincrement = (Long) YOUR_SESSION_OBJ.createSQLQuery(mysqlAutoincrementQuery)
                                                              .addScalar("id", Hibernate.LONG)
                                                              .setParameter("mysqlTableName", mysqlTableName)                                                              
                                                              .uniqueResult();
    

    【讨论】:

    • 你的理论是合理的,它确实帮助了我,但是你的 SQL 容易被注入。这从来都不好。
    【解决方案6】:

    您可以使用 Hibernate Dialect API 来实现数据库独立性,如下所示

    class SequenceValueGetter {
        private SessionFactory sessionFactory;
    
        // For Hibernate 3
        public Long getId(final String sequenceName) {
            final List<Long> ids = new ArrayList<Long>(1);
    
            sessionFactory.getCurrentSession().doWork(new Work() {
                public void execute(Connection connection) throws SQLException {
                    DialectResolver dialectResolver = new StandardDialectResolver();
                    Dialect dialect =  dialectResolver.resolveDialect(connection.getMetaData());
                    PreparedStatement preparedStatement = null;
                    ResultSet resultSet = null;
                    try {
                        preparedStatement = connection.prepareStatement( dialect.getSequenceNextValString(sequenceName));
                        resultSet = preparedStatement.executeQuery();
                        resultSet.next();
                        ids.add(resultSet.getLong(1));
                    }catch (SQLException e) {
                        throw e;
                    } finally {
                        if(preparedStatement != null) {
                            preparedStatement.close();
                        }
                        if(resultSet != null) {
                            resultSet.close();
                        }
                    }
                }
            });
            return ids.get(0);
        }
    
        // For Hibernate 4
        public Long getID(final String sequenceName) {
            ReturningWork<Long> maxReturningWork = new ReturningWork<Long>() {
                @Override
                public Long execute(Connection connection) throws SQLException {
                    DialectResolver dialectResolver = new StandardDialectResolver();
                    Dialect dialect =  dialectResolver.resolveDialect(connection.getMetaData());
                    PreparedStatement preparedStatement = null;
                    ResultSet resultSet = null;
                    try {
                        preparedStatement = connection.prepareStatement( dialect.getSequenceNextValString(sequenceName));
                        resultSet = preparedStatement.executeQuery();
                        resultSet.next();
                        return resultSet.getLong(1);
                    }catch (SQLException e) {
                        throw e;
                    } finally {
                        if(preparedStatement != null) {
                            preparedStatement.close();
                        }
                        if(resultSet != null) {
                            resultSet.close();
                        }
                    }
    
                }
            };
            Long maxRecord = sessionFactory.getCurrentSession().doReturningWork(maxReturningWork);
            return maxRecord;
        }
    
    }
    

    【讨论】:

    • 这很可爱,除了
    • +1 我认为这是发布的唯一与数据库无关的答案,这正是我所需要的。 (我们有一个使用 Hibernate 4 的应用程序,它需要从 db 序列中获取非 id 值,使用 HSQLDB 进行测试,使用 Oracle 进行真正的交易。)使用 java 7 的 try-with-resources 可以缩短代码少量。在将resolveDialect() 传递给connection.getMetaData() 之前,我确实必须使用DatabaseMetaDataDialectResolutionInfoAdapter 来包装connection.getMetaData() 的结果。 @punitpatel 谢谢!
    • 其实,我必须补充一点,这使用dialect.getSequenceNextValString(),它不适用于不支持序列的数据库...
    • 您可能还想使用 doReturningWork(可能比 3 更新),getSequenceNextValString(...) 不引用或以其他方式验证序列名称,并且在这个评论。所以不要从用户输入中获取序列名称,这无论如何都不太可能。
    【解决方案7】:

    这是我的做法:

    @Entity
    public class ServerInstanceSeq
    {
        @Id //mysql bigint(20)
        @SequenceGenerator(name="ServerInstanceIdSeqName", sequenceName="ServerInstanceIdSeq", allocationSize=20)
        @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ServerInstanceIdSeqName")
        public Long id;
    
    }
    
    ServerInstanceSeq sis = new ServerInstanceSeq();
    session.beginTransaction();
    session.save(sis);
    session.getTransaction().commit();
    System.out.println("sis.id after save: "+sis.id);
    

    【讨论】:

      【解决方案8】:

      有趣的是它对你有用。当我尝试您的解决方案时,出现错误,说“类型不匹配:无法从 SQLQuery 转换为查询”。 --> 因此我的解决方案如下:

      SQLQuery query = session.createSQLQuery("select nextval('SEQUENCE_NAME')");
      Long nextValue = ((BigInteger)query.uniqueResult()).longValue();
      

      使用该解决方案,我没有遇到性能问题。

      如果您只是想了解信息,请不要忘记重置您的值。

          --nextValue;
          query = session.createSQLQuery("select setval('SEQUENCE_NAME'," + nextValue + ")");
      

      【讨论】:

        【解决方案9】:

        你对 SequenceGenerator 假实体的想法很好。

        @Id
        @GenericGenerator(name = "my_seq", strategy = "sequence", parameters = {
                @org.hibernate.annotations.Parameter(name = "sequence_name", value = "MY_CUSTOM_NAMED_SQN"),
        })
        @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_seq")
        

        使用键名为“sequence_name”的参数很重要。在休眠类 SequenceStyleGenerator 上运行调试会话,configure(...) 方法在 final QualifiedName sequenceName = determineSequenceName( params, dialect, jdbcEnvironment ); 行查看有关 Hibernate 如何计算序列名称的更多详细信息。您也可以使用其中的一些默认值。

        在伪造实体之后,我创建了一个 CrudRepository:

        public interface SequenceRepository extends CrudRepository<SequenceGenerator, Long> {}
        

        在Junit中,我调用了SequenceRepository的save方法。

        SequenceGenerator sequenceObject = new SequenceGenerator(); SequenceGenerator 结果 = sequenceRepository.save(sequenceObject);

        如果有更好的方法来做到这一点(也许支持在任何类型的字段上生成生成器,而不仅仅是 Id),我会非常乐意使用它而不是这个“技巧”。

        【讨论】:

          【解决方案10】:

          Spring 5 有一些内置的帮助类: org/springframework/jdbc/support/incrementer

          【讨论】: