34

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

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

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

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

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

@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 的内部功能,以及如何手动重现它?

4

10 回答 10

29

您可以使用 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;
    }

}
于 2014-04-24T11:10:15.997 回答
25

这对我有用(特定于 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

于 2012-01-20T23:32:55.267 回答
6

我找到了解决方案:

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() ;
        }

}
于 2011-06-20T13:15:02.000 回答
3

如果您使用的是 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;
于 2011-06-17T14:02:30.187 回答
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();
    }
}
于 2011-06-17T14:05:42.013 回答
1

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();
于 2013-07-09T08:27:53.947 回答
1

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

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

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

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

    --nextValue;
    query = session.createSQLQuery("select setval('SEQUENCE_NAME'," + nextValue + ")");
于 2016-08-04T08:04:40.107 回答
1

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

于 2019-07-16T15:44:44.527 回答
0

您对 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),我会非常乐意使用它而不是这个“技巧”。

于 2017-08-10T15:12:30.310 回答
0

这是我的做法:

@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);
于 2016-03-10T18:52:11.407 回答