Получить следующее значение последовательности из базы данных, используя спящий режим
У меня есть объект, у которого есть поле NON-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;
}
}
Однако этот подход не сработал (все возвращенные идентификаторы были равны 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 и как его воспроизвести вручную?
Ответы
Ответ 1
Я нашел решение:
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() ;
}
}
Ответ 2
Вот что сработало для меня (специфическое для 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
Ответ 3
Вы можете использовать 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;
}
}
Ответ 4
Если вы используете Oracle, попробуйте указать размер кеша для последовательности. Если вы регулярно создаете объекты в пакетах 5K, вы можете просто установить его на 1000 или 5000. Мы сделали это для последовательности, используемой для первичного первичного ключа, и были поражены тем, что время выполнения для процесса ETL, написанного на Java, упало пополам.
Я не могу вставить форматированный код в комментарий. Здесь последовательность DDL:
create sequence seq_mytable_sid
minvalue 1
maxvalue 999999999999999999999999999
increment by 1
start with 1
cache 1000
order
nocycle;
Ответ 5
Чтобы получить новый идентификатор, все, что вам нужно сделать, это 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();
}
}
Ответ 6
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();
Ответ 7
Интересно, что это работает для вас. Когда я попробовал ваше решение, возникла ошибка, заявив, что "Тип несоответствия: невозможно преобразовать из SQLQuery в Query". → Поэтому мое решение выглядит так:
SQLQuery query = session.createSQLQuery("select nextval('SEQUENCE_NAME')");
Long nextValue = ((BigInteger)query.uniqueResult()).longValue();
С этим решением я не столкнулся с проблемами производительности.
И не забудьте reset ваше значение, если вы просто хотели узнать в информационных целях.
--nextValue;
query = session.createSQLQuery("select setval('SEQUENCE_NAME'," + nextValue + ")");
Ответ 8
Вот как я это делаю:
@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);
Ответ 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". Запустите сеанс отладки в классе hibernate SequenceStyleGenerator, метод configure (...) в строке final QualifiedName sequenceName = defineSequenceName (params, dialect, jdbcEnvironment); чтобы узнать больше о том, как имя последовательности вычисляется Hibernate. Существуют некоторые значения по умолчанию, которые вы также можете использовать.
После поддельного объекта я создал CrudRepository:
public interface SequenceRepository extends CrudRepository<SequenceGenerator, Long> {}
В Junit я вызываю метод сохранения SequenceRepository.
Последовательность Последовательность генератораObject = new SequenceGenerator(); Результат последовательности SequenceGenerator = sequenceRepository.save(sequenceObject);
Если есть лучший способ сделать это (возможно, поддержка генератора для любого типа поля вместо Id), я был бы более чем счастлив использовать его вместо этого "трюка".
Ответ 10
В Spring 5 есть несколько встроенных вспомогательных классов для этого: org/springframework/jdbc/support/incrementer