Hibernate, @SequenceGenerator и allocationSize

117

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

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

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

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

Меня интересуют идентификаторы, которые не соответствуют базовой последовательности базы данных. Например: любое другое приложение (например, использующее простой 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>
Г. Демеки
источник
2
"без установки allocationSize = 1 и, таким образом, ухудшения производительности", почему это снижает производительность, если вы устанавливаете его на 1?
sheidaei 05
3
@sheidaei см. можете прокомментировать ниже :-) Это потому, что каждому saveнужно запросить базу данных для следующего значения последовательности.
G. Demecki 05
Спасибо, что столкнулись с той же проблемой. Сначала я добавлял allocationSize = 1 в каждый @SequenceGenerator. Использование hibernate.id.new_generator_mappings = true предотвратит это. Хотя JPA все еще запрашивает базу данных, чтобы получить идентификатор для каждой вставки ...
TheBakker
1
С SequenceGeneratorHibernate будет запрашивать базу данных только тогда, когда количество идентификаторов, указанных в, allocationsizeзаканчивается. Если вы настроили, allocationSize = 1то это причина, по которой Hibernate запрашивает БД для каждой вставки. Измените это значение, и все готово.
G. Demecki
1
Спасибо! hibernate.id.new_generator_mappingsустановка действительно важно. Я надеюсь, что это настройка по умолчанию, и мне не придется тратить так много времени на изучение того, почему идентификационный номер сходит с ума.
LeOn - Хан Ли

Ответы:

44

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

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

Стив Эберсол
источник
Спасибо, Стив! Лучший ответ. Также был полезен ваш другой пост .
G. Demecki 08
4
Я также заметил, что вы являетесь соавтором org.hibernate.id.enhanced.SequenceStyleGenerator. Вы меня удивили.
G. Demecki 08
22
Вы чем удивились? Я ведущий разработчик Hibernate. Я написал / соавтор многих классов Hibernate;)
Стив Эберсол
Только для записи. Следует избегать увеличения последовательности БД, чтобы не допустить больших пропусков. Последовательность БД умножается на allocationSize, когда заканчивается кеш ID. Подробнее stackoverflow.com/questions/5346147/…
Olcay Tarazan
1
Один из способов изменить «оптимизатор», используемый глобально, - это добавить что-то вроде этого в ваши параметры спящего режима: serviceBuilder.applySetting ("hibernate.id.optimizer.pooled.preferred", LegacyHiLoAlgorithmOptimizer.class.getName ()); Вместо LegacyHiLoAlgorithOptimizer вы можете выбрать любой класс оптимизатора, и он станет классом по умолчанию. Это должно упростить сохранение желаемого поведения по умолчанию без изменения всех аннотаций. Кроме того, будьте осторожны с оптимизаторами "pooled" и "hilo": они дают странные результаты, когда значение вашей последовательности начинается с 0, вызывая отрицательные идентификаторы.
fjalvingh
17

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

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

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

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

Амит Дешпанде
источник
12
Ничего общего с производительностью? Вы действительно уверены? Меня учили, что с allocationSize=1Hibernate при каждой saveоперации необходимо совершать переход к базе данных, чтобы получить новое значение идентификатора.
G. Demecki 05
2
Это микро-оптимизация перед получением запроса Hibernate пытается присвоить значение в диапазоне, allocationSizeпоэтому старайтесь избегать запросов к базе данных для последовательности. Но этот запрос будет выполняться каждый раз, если вы установите его на 1. Это вряд ли имеет какое-либо значение, поскольку если к вашей базе данных обращается какое-то другое приложение, это создаст проблемы, если тот же идентификатор будет использоваться другим приложением,
Амит Дешпанде,
И да, это полностью зависит от приложения, оказывает ли размер выделения 1 реальное влияние на производительность. Конечно, в микробенчмарке это всегда будет иметь огромное влияние; в этом проблема большинства тестов (микро или других), они просто нереалистичны. И даже если они достаточно сложны, чтобы быть в какой-то степени реалистичными, вам все равно нужно посмотреть, насколько близок тест к вашему реальному приложению, чтобы понять, насколько результаты теста применимы к результатам, которые вы увидите в своем приложении. Короче говоря ... проверьте это на себе
Стив Эберсол
2
ХОРОШО. Все зависит от приложения, не так ли! Если ваше приложение предназначено только для чтения, то влияние использования размера выделения 1000 или 1 будет абсолютно 0. С другой стороны, подобные вещи являются передовой практикой. Если вы не соблюдаете передовой опыт, который они собирают, в совокупности ваше приложение станет вялым. Другой пример - запуск транзакции, когда она вам абсолютно не нужна.
Хасан Джейлан,
1

Стив Эберсол и другие участники, не
могли бы вы объяснить причину появления идентификатора с большим пробелом (по умолчанию 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 становится намного больше. Итак, мой id во время тестирования с частым перезапуском сервера генерирует следующие идентификаторы последовательности:
1, 2, 3, 4, 19, 250, 251, 252, 400, 550, 750, 751, 752, 850, 1100, 1150.

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

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

Jihwan

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

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

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

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

Jihwan
источник
1

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

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

Также проверьте ниже http://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html#identifiers-generators-sequence

@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;
Фатих Текин
источник
3
Извините, но это чрезвычайно подробный способ настройки чего-либо, который можно легко выразить с помощью двух параметров для всего Hibernate и, следовательно, для всех сущностей.
G. Demecki
правда, но когда я пробую другие способы, ни один из них не работал, если он у вас работает, можете прислать мне, как вы настроили
Фатих текин
Я обновил свой ответ - теперь он также включает рабочий пример. Хотя мой комментарий выше частично неверен: к сожалению, вы не можете установить ни один, allocationSizeни initialValueглобально для всех сущностей (если не используете только один генератор, но ИМХО это не очень читается).
G. Demecki
1
Спасибо за объяснение, но то, что вы написали выше, я пробовал, и он не работал с hibernate 5.0.7. Окончательная версия, затем я покопался в исходном коде, чтобы достичь этой цели, и это реализация, которую я смог найти в исходном коде гибернации. Настройка может выглядеть плохо, но, к сожалению, это приводит к гибернации api, и я использую стандартную реализацию EntityManager для гибернации
fatih tekin
1

Я тоже столкнулся с этой проблемой в 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. Теперь идентификатор генерируется в соответствии с последовательностью оракула.
Мохамед Афзал
источник
0

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

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

Хасан Джейлан
источник
Вы неправильно понимаете мою точку зрения. ALTER SEQUENCE ... INCREMENTY BY 50;ничего не решит, потому что проблема осталась прежней. Значение последовательности по-прежнему не отражает идентификаторы реальных сущностей.
G. Demecki 08
Поделитесь тестовым примером, чтобы мы могли лучше понять проблему.
Хасан Джейлан,
1
Прецедент? Зачем? Поставленный мной вопрос не был таким уж сложным, и на него уже был дан ответ. Похоже, вы не знаете, как работает генератор HiLo. В любом случае: спасибо за то, что пожертвовали своим временем и усилиями.
Г. Демеки
1
Грегори, на самом деле я знаю, о чем говорю, я написал Batoo JPA, который представляет собой реализацию JPA% 100, которая в настоящее время находится в стадии инкубации и превосходит Hibernate с точки зрения скорости - в 15 раз быстрее. С другой стороны, я мог неправильно понять ваш вопрос и не подумать, что использование Hibernate с последовательностями должно вообще создавать какие-либо проблемы, поскольку я использовал Hibernate с 2003 года во многих проектах во многих базах данных. Важно то, что у вас есть решение вопроса, извините, я пропустил ответ, помеченный как правильный ...
Хасан Джейлан
Извини, я не хотел тебя обидеть. Еще раз спасибо за вашу помощь, на вопрос дан ответ.
G. Demecki