Выбор стратегии аннотации @GeneratedValue при настройке генерации id в Hibernate

Для автоматической генерации id новых объектов сущностей Hibernate может использовать различные стратегии. Помимо тонко настраиваемых стратегий самого Hibernate, в спецификации JPA также доступны несколько стандартных стратегий, которые можно указать параметром strategy в аннотации @GeneratedValue над идентификационным полем. Здесь мы их и рассмотрим.

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

GenerationType.AUTO

Создадим класс предметной области со следующим содержимым:

Стратегия GenerationType.AUTO подразумевает, что Hibernate сам выберет подходящую стратегию генерации id. Обычно, если СУБД поддерживает сиквенсы, то Hibernate создаст отдельный сиквенс для этой сущности. Если сиквенсы не поддерживаются, то Hibernate создаст специальную таблицу, в которой будет хранить номер последнего созданного id и использовать эту таблицу в качестве аналога сиквенса.

Запустим проект, чтобы Hibernate создал для этой сущности таблицу и посмотрим состояние БД в консоли psql:

Как видим PostgreSQL поддерживает сиквенсы и Hibernate создаст для генерации id в нашей таблице именно сиквенс.

GenerationType.SEQUENCE

Создадим ещё один класс предметной области со следующим содержимым:

Стратегия GenerationType.SEQUENCE совершенно очевидным образом предполагает создание и использование в БД сиквенса для генерации новых id. Запустим проект и посмотрим, как изменится структура БД:

Без каких-либо сюрпризов для новой сущности был создан отдельный сиквенс.

GenerationType.IDENTITY

Создадим ещё один класс предметной области со следующим содержимым:

Стратегия GenerationType.IDENTITY предписывает Hibernate’у создать такой столбец первичного ключа, в котором бы при вставке БД автоматически генерировала новое значение. Во-первых, нужно, чтобы СУБД поддерживала такую функциональность (например, в старых версиях PostrgeSQL это не работало). Во-вторых, нужно помнить, что при таком подходе сначала происходит вставка, и лишь затем появляется значение id. Это менее предпочтительный вариант, чем когда сначала Hibernate генерирует id, а затем производит вставку. В последнем случае Hibernate имеет пространство для различных оптимизаций. В случае со стратегией GenerationType.IDENTITY никакие оптимизации вставки Hibernate’ом невозможны.

Запустим приложение и посмотрим, что у нас в базе:

Новая таблица и сиквенс появились в середине списка (так как список сортирован в алфавитном порядке). Может показаться, что и в данном случае Hibernate использует сиквенс, ведь как мы видим сиквенс всё-таки был создан. Однако это не совсем так. Этот созданный сиквенс использует PostgreSQL, а не Hibernate. Hibernate всё-равно узнает значение нового id только после вставки.

В этом легко убедится, выполнив такую команду:

Как мы видим у таблицы generation_type_sequence нет значения по умолчанию для колонки первичного ключа. Hibernate воспользуется сиквенсом. Получит значение нового id. И передаст его вместе с другими данными строки в операцию INSERT.

У таблицы generation_type_identity ситуация другая. Hibernate будет отправлять команду INSERT без значения id. И уже БД будет записывать сюда значение по умолчанию, которое формируется так, как это написано на иллюстрации, то есть задействуя некий сиквенс. Но это уже делает БД и делает это так, как считает нужным. Hibernate о том, как БД получает новый id, ничего не знает и знать не должен.

GenerationType.TABLE

Создадим последний в этом материале класс предметной области со следующим содержимым:

Следуя стратегии GenerationType.TABLE Hibernate создаст специальную таблицу, в которой будет содержаться последний сгенерированный id. Когда понадобится следующий номер id, последний номер будет увеличен на единицу и записан в таблицу как последний.

Схема нехитрая. У Hibernate’а есть возможность создавать кастомные генераторы с более тонкими настройками, в том числе подобных таблиц. Переживать за то, что чисел на все записи не хватит, не нужно. Даже если будет сквозной id для всех таблиц и таблицы будут большими. Для генерации id используется 19-значное целое, соответствующее типу long в Java. Это означает, если вы будете генерировать новый id каждую миллисекунду, ну, где-то 300 миллионов лет уйдёт, чтобы исчерпать все доступные числа.

Запустим приложение и посмотрим состояние БД:

Видно, что Hibernate создал таблицу hibernate_sequence и по мере использования БД можно убедится, что последний номер id хранится именно в ней.

Заключение

Не все СУБД поддерживают все стратегии. В общем случае можно полагаться на стратегию GenerationType.AUTO. А значит просто использовать аннотацию @GeneratedValue без параметров, так она AUTO используется по умолчанию.

Она приведёт либо к созданию сиквенса, если СУБД их поддерживает, либо к созданию специальной служебной таблицы. В обоих случаях Hibernate будет генерировать новые id ещё до вставки командой INSERT, что позволит ему использовать различные оптимизации (пакетная вставка для вставки нескольких строк, например). Обычно это является предпочтительным вариантом.