Hibernate, @SequenceGenerator и allocSize

Мы все знаем поведение Hibernate по умолчанию при использовании @SequenceGenerator - увеличивает реальную последовательность базы данных на единицу, умножает это значение на 50 (по умолчанию allocationSize значение) - а затем использует это значение в качестве идентификатора объекта.

Это некорректное поведение и противоречит спецификации, которая гласит:

allocSize - (Необязательно) Величина, на которую увеличивается значение при выделении порядковых номеров из последовательности.

Чтобы было ясно: я не беспокоюсь о пробелах между сгенерированными идентификаторами.

Я забочусь о идентификаторах, которые не соответствуют базовой последовательности базы данных. Например: любое другое приложение (которое, например, использует простой JDBC) может захотеть вставить новые строки под идентификаторами, полученными из последовательности - но все эти значения могут уже использоваться Hibernate! Безумие.

Кто-нибудь знает какое-либо решение этой проблемы (без настройки allocationSize=1 и, таким образом, снижение производительности)?

РЕДАКТИРОВАТЬ:
Чтобы было ясно. Если у последней вставленной записи был ID = 1, то HB используйте значения 51, 52, 53... для его новых объектов, НО в то же время: значение последовательности в базе данных будет установлено на 2, Что может легко привести к ошибкам, когда другие приложения используют эту последовательность.

С другой стороны: спецификация говорит (в моем понимании), что последовательность базы данных должна была быть установлена ​​в 51 и тем временем HB должен использовать значения из диапазона 2, 3 ... 50


ОБНОВИТЬ:
Как упомянул Стив Эберсоле ниже: описанное мной поведение (а также наиболее интуитивное для многих) можно включить, установив hibernate.id.new_generator_mappings=true,

Спасибо всем вам.

ОБНОВЛЕНИЕ 2:
Для будущих читателей ниже вы можете найти рабочий пример.

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USERS_SEQ")
    @SequenceGenerator(name = "USERS_SEQ", sequenceName = "SEQUENCE_USERS")
    private Long id;
}

persistence.xml

<persistence-unit name="testPU">
  <properties>
    <property name="hibernate.id.new_generator_mappings" value="true" />
  </properties>
</persistence-unit>

5 ответов

Решение

Чтобы быть абсолютно ясным... то, что вы описываете, никак не противоречит спецификации. В спецификации говорится о значениях, которые Hibernate назначает вашим сущностям, а не о значениях, фактически сохраненных в последовательности базы данных.

Тем не менее, есть возможность получить поведение, которое вы ищете. Сначала посмотрите мой ответ на вопрос: есть ли способ динамического выбора стратегии @GeneratedValue с использованием аннотаций JPA и Hibernate? Это даст вам основы. Пока вы настроены на использование этого SequenceStyleGenerator, Hibernate будет интерпретировать allocationSize используя "объединенный оптимизатор" в SequenceStyleGenerator. "Оптимизатор пула" предназначен для использования с базами данных, которые допускают опцию "увеличения" при создании последовательностей (не все базы данных, которые поддерживают последовательности, поддерживают приращение). В любом случае, прочитайте о различных стратегиях оптимизатора там.

allocationSize=1 Это микрооптимизация перед получением запроса. Hibernate пытается присвоить значение в диапазоне allocSize и поэтому старается не запрашивать базу данных для последовательности. Но этот запрос будет выполняться каждый раз, если вы установите его на 1. Это вряд ли будет иметь какое-либо значение, поскольку, если к вашей базе данных получит доступ какое-то другое приложение, то это создаст проблемы, если этот же идентификатор используется другим приложением.

Следующее поколение Sequence Id основано на allocSize.

По умолчанию это сохраняется как 50 что слишком много. Это также поможет, только если вы собираетесь иметь около 50 записи в одном сеансе, которые не сохраняются и которые будут сохраняться с использованием этого конкретного сеанса и перехода.

Поэтому вы всегда должны использовать allocationSize=1 при использовании SequenceGenerator, Что касается большинства базовых баз данных, последовательность всегда увеличивается на 1,

Я бы проверил DDL для последовательности в схеме. Реализация JPA отвечает только за создание последовательности с правильным размером выделения. Поэтому, если размер выделения равен 50, ваша последовательность должна иметь приращение 50 в своем DDL.

Этот случай может обычно происходить при создании последовательности с размером выделения 1, который затем конфигурируется для размера выделения 50 (или по умолчанию), но DDL последовательности не обновляется.

Я тоже столкнулся с этой проблемой в Hibernate 5:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
@SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE)
private Long titId;

Получил предупреждение, подобное приведенному ниже:

Обнаружено использование устаревшего генератора идентификаторов последовательностей [org.hibernate.id.SequenceHiLoGenerator]; используйте вместо этого org.hibernate.id.enhanced.SequenceStyleGenerator. Подробнее см. Руководство по сопоставлению модели домена Hibernate.

Затем изменил мой код на SequenceStyleGenerator:

@Id
@GenericGenerator(name="cmrSeq", strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
        parameters = {
                @Parameter(name = "sequence_name", value = "SEQUENCE")}
)
@GeneratedValue(generator = "sequence_name")
private Long titId;

Это решило мои две проблемы:

  1. Устарелое предупреждение исправлено
  2. Теперь идентификатор генерируется в соответствии с последовательностью оракула.

Стив Эберсоле и другие участники,
Не могли бы вы объяснить причину идентификатора с большим разрывом (по умолчанию 50)? Я использую Hibernate 4.2.15 и нашел следующий код в org.hibernate.id.enhanced.OptimizerFactory cass.

if ( lo > maxLo ) {
   lastSourceValue = callback.getNextValue();
   lo = lastSourceValue.eq( 0 ) ? 1 : 0;
   hi = lastSourceValue.copy().multiplyBy( maxLo+1 ); 
}  
value = hi.copy().add( lo++ );

Всякий раз, когда он попадает внутрь оператора if, значение hi становится намного больше. Итак, мой идентификатор во время тестирования с частым перезапуском сервера генерирует следующие идентификаторы последовательности:
1, 2, 3, 4, 19, 250, 251, 252, 400, 550, 750, 751, 752, 850, 1100, 1150.

Я знаю, что вы уже сказали, что это не противоречит спецификации, но я считаю, что это будет очень неожиданная ситуация для большинства разработчиков.

Любой вклад будет очень полезным.

Jihwan

ОБНОВЛЕНИЕ: ne1410s: Спасибо за редактирование.
cfrick: хорошо. Я сделаю это. Это был мой первый пост здесь, и я не знал, как его использовать.

Теперь я лучше понял, почему maxLo использовался для двух целей: поскольку hibernate вызывает последовательность БД один раз, продолжает увеличивать идентификатор на уровне Java и сохраняет его в БД, значение идентификатора уровня Java должно учитывать, сколько было изменено без вызова последовательность DB, когда она вызывает последовательность в следующий раз.

Например, идентификатор последовательности равнялся 1 в точке, а в спящем режиме вводилось 5, 6, 7, 8, 9 (с allocSize = 5). В следующий раз, когда мы получим следующий порядковый номер, DB возвращает 2, но hibernate должен использовать 10, 11, 12... Так вот почему "hi = lastSourceValue.copy(). MultiplyBy( maxLo+1)" используется для получения следующего идентификатора 10 из 2, возвращенного из последовательности DB. Кажется, единственное беспокойство было во время частого перезапуска сервера, и это была моя проблема с большим разрывом.

Таким образом, когда мы используем SEQUENCE ID, вставленный идентификатор в таблице не будет совпадать с номером SEQUENCE в БД.

После копания в исходном коде гибернации и конфигурации ниже, Oracle переходит к БД для следующего значения после 50 вставок. Так что делайте ваш INST_PK_SEQ с шагом 50 каждый раз, когда он вызывается.

Hibernate 5 используется для стратегии ниже

Проверьте также ниже http://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html

@Id
@Column(name = "ID")
@GenericGenerator(name = "INST_PK_SEQ", 
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
        @org.hibernate.annotations.Parameter(
                name = "optimizer", value = "pooled-lo"),
        @org.hibernate.annotations.Parameter(
                name = "initial_value", value = "1"),
        @org.hibernate.annotations.Parameter(
                name = "increment_size", value = "50"),
        @org.hibernate.annotations.Parameter(
                name = SequenceStyleGenerator.SEQUENCE_PARAM, value = "INST_PK_SEQ"),
    }
)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "INST_PK_SEQ")
private Long id;
Другие вопросы по тегам