シーケンスから設定する必要がある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のsomehowは、_@GeneratedValue
_で記述されたシーケンスにアクセスする際のフェッチ数を削減します。
たとえば、70,000個のエンティティを作成すると(したがって、同じシーケンスから70,000個の主キーが取得されます)、必要なものはすべて取得されます。
[〜#〜] however [〜#〜]、Hibernateのみが発行する1404select nextval ('local_key_sequence')
コマンド。注:データベース側では、キャッシュは1に設定されています。
すべてのデータを手動で取得しようとすると、70,000回の選択が必要になり、パフォーマンスが大きく異なります。 Hibernateの内部機能と、それを手動で再現する方法を知っている人はいますか?
私は解決策を見つけました:
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() ;
}
}
ここに私のために働いたものがあります(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
次のように、データベースの独立性のために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;
}
}
Oracleを使用している場合は、シーケンスのキャッシュサイズを指定することを検討してください。 5Kのバッチで定期的にオブジェクトを作成している場合は、1000または5000に設定するだけです。代理主キーに使用されるシーケンスに対してそれを行い、Javaは半分になりました。
フォーマットされたコードをコメントに貼り付けることができませんでした。シーケンスDDLは次のとおりです。
create sequence seq_mytable_sid
minvalue 1
maxvalue 999999999999999999999999999
increment by 1
start with 1
cache 1000
order
nocycle;
新しい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();
}
}
おもしろいですね。ソリューションを試してみると、「型の不一致:SQLQueryからクエリに変換できません」というエラーが表示されました。 ->したがって、私のソリューションは次のようになります。
SQLQuery query = session.createSQLQuery("select nextval('SEQUENCE_NAME')");
Long nextValue = ((BigInteger)query.uniqueResult()).longValue();
そのソリューションでは、パフォーマンスの問題は発生しませんでした。
また、情報目的で知りたいだけの場合は、値をリセットすることを忘れないでください。
--nextValue;
query = session.createSQLQuery("select setval('SEQUENCE_NAME'," + nextValue + ")");
[〜#〜] 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();
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クラスSequenceDebugGeneratorでデバッグセッションを実行し、最後の行でconfigure(...)メソッドを実行します。 Hibernateによるシーケンス名の計算方法の詳細を確認するには。そこにいくつかのデフォルトがあり、使用することもできます。
偽のエンティティの後、CrudRepositoryを作成しました。
public interface SequenceRepository extends CrudRepository<SequenceGenerator, Long> {}
Junitでは、SequenceRepositoryのsaveメソッドを呼び出します。
SequenceGenerator sequenceObject = new SequenceGenerator(); SequenceGenerator result = sequenceRepository.save(sequenceObject);
これを行うためのより良い方法がある場合(おそらく、Idだけでなく、あらゆるタイプのフィールドでのジェネレーターのサポート)、この「トリック」の代わりにそれを使用するのはうれしいことです。
ここに私がそれをする方法があります:
@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);
Spring 5には、そのための組み込みヘルパークラスがいくつかあります。 org/springframework/jdbc/support/incrementer