Подтвердить что ты не робот

Получить следующее значение последовательности из базы данных, используя спящий режим

У меня есть объект, у которого есть поле 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 и как его воспроизвести вручную?

4b9b3361

Ответ 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), я был бы более чем счастлив использовать его вместо этого "трюка".