Создание кастомного генератора id через аннотацию @GenericGenerator в Hibernate

Обычно в качестве генератора id используется сиквенс базы данных, который Hibernate создаёт автоматически, когда мы указываем над идентификационным полем аннотацию @GeneratedValue(strategy = GenerationType.SEQUENCE) или @GeneratedValue(strategy = GenerationType.AUTO). Этот сиквенс можно настроить: задать ему имя и первоначальное значение. Кроме того, вместо сиквенса можно настроить использование других методов генерации id, если, например, СУБД не поддерживает синквенсы.

Создадим базовое веб-приложения на связке Spring Boot 3 + Hibernate + PostgreSQL.

Использование аннотации @GenericGenerator

Id поле класса предметной области может ссылаться на кастомный генератор id следующим образом:

Теперь, чтобы этот код работал нам нужно создать этот самый CUSTOM_GENERATOR. Чтобы это сделать достаточно просто объявить аннотацию @org.hibernate.annotations.GenericGenerator и передать в неё определённые набор параметров.

Создать эту аннотацию можно над id полем (прямо под @GeneratedValue), а также над классом или над пакетом в файле настроек пакета package-info.java. Чаще всего, если конкретный генератор будет использоваться только одной сущностью, то аннотацию с настройками генератора ставят над id полем. Если же генератор предназначен для использования несколькими сущностями, то его помещают в настройки пакета в файл package-info.java.

Аннотация @org.hibernate.annotations.GenericGenerator принимает два обязательных параметра:

  • name. Задаёт имя генератора. Собственно это имя мы и указываем затем в аннотации @GeneratedValue(generator = "CUSTOM_GENERATOR"), если хотим, чтобы класс предметной области использовал этот генератор.
  • strategy. Определяет способ генерирования id.

Стратегия указывается либо через полное имя класса стратегии:

strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator"

либо через альяс:

strategy = "enhanced-sequence"

Далее можно передать необязательный параметр parameters, который представляет собой массив аннотаций типа @org.hibernate.annotations.Parameter.

Каждая аннотация типа @org.hibernate.annotations.Parameter в свою очередь также принимает по два параметра:

  • name. Имя параметра.
  • value. Значение параметра.

Какие параметры имеет смысл передавать в генератор и какие значения они могут принимать, зависит от стратегии. Посмотреть список основных параметров, которые может принимать та или иная стратегия довольно просто. Нужно в любом (даже неподходящем) месте программы ввести имя_класса_стратегии.class. А затем посмотреть javadoc этого класса:

В джавадоке перечислены параметры в виде строковых констант (например, SEQUENCE_PARAM), но почему-то очень распространён вариант передавать параметры в виде непосредственно строк (например, "sequence_name"), как в нашем примере.

Посмотреть полный список констант и их строковые значения можно здесь: https://docs.jboss.org/hibernate/orm/6.1/javadocs/constant-values.html. Обратите внимание, что в этом урле можно просто менять версию Хибернейта и смотреть таблицы констант для любой нужной версии. Практически всю нужную информацию можно найти и в джавадоках, не выходя из IDE, но в браузере как-то обычно поудобней с навигацией.

Также заметим, что интерфейс OptimizableGenerator содержит параметры общие для разных стратегий. Поэтому хотя в списке параметров стратегии org.hibernate.id.enhanced.SequenceStyleGenerator нет параметра INITIAL_PARAM/"initial_value". Мы его успешно передаём и он успешно работает, так как стратегия org.hibernate.id.enhanced.SequenceStyleGenerator принимает все параметры для org.hibernate.id.OptimizableGenerator.

И, конечно, никто не запрещает импортировать константы:

Но по каким-то причинам часто пишут именно name = "sequence_name", а не name = SEQUENCE_PARAM. Возможно потому, что строковый вариант более выразительный.

Выбор стратегии

В аннотацию @org.hibernate.annotations.GenericGenerator мы должны обязательно передать параметр strategy, например strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator". Список доступных стратегий можно посмотреть на странице https://docs.jboss.org/hibernate/orm/6.0/javadocs/org/hibernate/id/package-summary.html (причём здесь также можно поменять версию, например, на 3.5 и увидеть, что в предыдущих версиях Хибернейта были и другие стратегии).

Но наибольший интерес представляют имплементации интерфейса org.hibernate.id.IdentifierGenerator (раздел All Known Implementing Classes).

В общем случае можно всегда выбрать стратегию org.hibernate.id.enhanced.SequenceStyleGenerator (enhanced-sequence). По этой стратегии Хибернейт будет всегда использовать сиквенс, если СУБД их поддерживает. Если нет, то Хибернейт создаст специальную таблицу HIBERNATE_SEQUENCE, которая будет эмулировать работу сиквенса. В рамках этой стратегии Хибернейт всегда будет генерировать очередной id до выполнения команды INSERT. Эту стратегию можно считать универсальной и переносимой, так как её скорее всего не придётся менять при переезде на другую СУБД.

Собственно ключевым вопросом выбора стратегии — является вопрос о том, когда генерируется id. Предпочтительным является случай, когда id создаётся до фактической вставки. Но если в БД заведены таблицы с полями с автоинкрементом, то нужно использовать стратегию org.hibernate.id.IdentityGenerator. В этом случае ДБ вернёт id уже после вставки, что считается менее предпочтительным вариантом.

Среди распространённых стратегий также можно выделить:

org.hibernate.id.enhanced.TableGenerator. Генерирует id на основе значений в специально созданной таблице. Современная версия генератора может создавать отдельную строку для хранения последнего id для каждой сущности. В старых версиях строка всегда была одна и нумерация id была сквозной для всех сущностей.

org.hibernate.id.uuid.UuidGenerator. Генерирует id вида 5f4aa565-b0b3-41b6-8d05-e0c6b9263160. Такой id будет уникальным за пределами одной своей таблицы.

org.hibernate.id.IncrementGenerator. Найдёт в столбце первичного ключа самое больше значение. Увеличит его на единицу и вернёт как новый id.

Другие стратегии и их описания можно посмотреть по ссылкам выше.