Обычно в качестве генератора id используется сиквенс базы данных, который Hibernate создаёт автоматически, когда мы указываем над идентификационным полем аннотацию @GeneratedValue(strategy = GenerationType.SEQUENCE) или @GeneratedValue(strategy = GenerationType.AUTO). Этот сиквенс можно настроить: задать ему имя и первоначальное значение. Кроме того, вместо сиквенса можно настроить использование других методов генерации id, если, например, СУБД не поддерживает синквенсы.
Создадим базовое веб-приложения на связке Spring Boot 3 + Hibernate + PostgreSQL.
Использование аннотации @GenericGenerator
Id поле класса предметной области может ссылаться на кастомный генератор id следующим образом:
1 2 3 4 5 6 7 |
@Entity public class Person { @Id @GeneratedValue(generator = "CUSTOM_GENERATOR") private Long id; .... |
Теперь, чтобы этот код работал нам нужно создать этот самый CUSTOM_GENERATOR. Чтобы это сделать достаточно просто объявить аннотацию @org.hibernate.annotations.GenericGenerator
и передать в неё определённые набор параметров.
Создать эту аннотацию можно над id полем (прямо под @GeneratedValue), а также над классом или над пакетом в файле настроек пакета package-info.java. Чаще всего, если конкретный генератор будет использоваться только одной сущностью, то аннотацию с настройками генератора ставят над id полем. Если же генератор предназначен для использования несколькими сущностями, то его помещают в настройки пакета в файл package-info.java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@org.hibernate.annotations.GenericGenerator( name = "CUSTOM_GENERATOR", strategy = "enhanced-sequence", //org.hibernate.id.enhanced.SequenceStyleGenerator parameters = { @org.hibernate.annotations.Parameter( name = "sequence_name", value = "custom_sequence_name" ), @org.hibernate.annotations.Parameter( name = "initial_value", value = "1000" ) } ) |
Аннотация @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.
И, конечно, никто не запрещает импортировать константы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import static org.hibernate.id.OptimizableGenerator.INITIAL_PARAM; import static org.hibernate.id.enhanced.SequenceStyleGenerator.SEQUENCE_PARAM; @org.hibernate.annotations.GenericGenerator( name = "CUSTOM_GENERATOR", strategy = "enhanced-sequence", parameters = { @org.hibernate.annotations.Parameter( name = SEQUENCE_PARAM, value = "custom_sequence_name" ), @org.hibernate.annotations.Parameter( name = INITIAL_PARAM, value = "1000" ) } ) |
Но по каким-то причинам часто пишут именно 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.
Другие стратегии и их описания можно посмотреть по ссылкам выше.