我有一个具有NON-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个主键),我得到了我需要的一切。
HOWEVER ,Hibernate仅发出 1404 select nextval ('local_key_sequence')
命令。注意:在数据库端,缓存设置为1.
如果我尝试手动获取所有数据,则需要70,000次选择,因此性能差异很大。有谁知道Hibernate的内部功能,以及如何手动重现它?
答案 0 :(得分:24)
这对我有用(特定于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
答案 1 :(得分:19)
您可以使用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;
}
}
答案 2 :(得分:5)
我找到了解决方案:
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() ;
}
}
答案 3 :(得分: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;
答案 4 :(得分: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();
}
}
答案 5 :(得分: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();
答案 6 :(得分:1)
有趣的是它适合你。当我尝试你的解决方案时出现错误,说“类型不匹配:无法从SQLQuery转换为查询”。 - &GT;因此,我的解决方案如下:
SQLQuery query = session.createSQLQuery("select nextval('SEQUENCE_NAME')");
Long nextValue = ((BigInteger)query.uniqueResult()).longValue();
通过该解决方案,我没有遇到性能问题。
如果您只是想了解相关信息,请不要忘记重置您的价值。
--nextValue;
query = session.createSQLQuery("select setval('SEQUENCE_NAME'," + nextValue + ")");
答案 7 :(得分: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);
答案 8 :(得分: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”的参数非常重要。在hibernate类SequenceStyleGenerator上运行调试会话,最后一行QualifiedName sequenceName = determineSequenceName(params,dialect,jdbcEnvironment)中的configure(...)方法;查看有关Hibernate如何计算序列名称的更多详细信息。你可以使用一些默认值。
在假实体之后,我创建了一个CrudRepository:
public interface SequenceRepository extends CrudRepository<SequenceGenerator, Long> {}
在Junit中,我调用了SequenceRepository的save方法。
SequenceGenerator sequenceObject = new SequenceGenerator(); SequenceGenerator result = sequenceRepository.save(sequenceObject);
如果有更好的方法(可能支持任何类型的字段上的生成器而不是Id),我会非常乐意使用它而不是这个“技巧”。
答案 9 :(得分:0)
Spring 5为此提供了一些内置的帮助程序类: org/springframework/jdbc/support/incrementer