Идентификаторы
Каждой сущности нужен первичный ключ — идентификатор, уникальный в пределах таблицы. Обычно программа его не придумывает: его генерирует база данных или Hibernate. Как именно, задаёт параметр strategy аннотации @GeneratedValue, и от выбора зависит, когда выполняется insert.
Стратегии
| Стратегия | Кто создаёт значение | Столбец |
|---|---|---|
AUTO (по умолчанию) | Выбирает Hibernate; для H2, PostgreSQL и Oracle это последовательность | bigint |
SEQUENCE | Последовательность (sequence) в базе | bigint |
IDENTITY | База, как столбец с автоувеличением | bigint generated by default as identity |
UUID | Hibernate, случайный UUID | uuid |
Три сущности, три стратегии
// src/main/java/com/example/blog/Post.java
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
// конструкторы и геттеры
}// src/main/java/com/example/blog/Comment.java
@Entity
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@SequenceGenerator(sequenceName = "comment_seq", allocationSize = 10)
private Long id;
private String text;
// конструкторы и геттеры
}// src/main/java/com/example/blog/Tag.java
@Entity
public class Tag {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private String name;
// конструкторы и геттеры
}Аннотации импортируются из jakarta.persistence, UUID — из java.util. Программа сохраняет одну статью, три комментария и тег и печатает их id сразу после persist:
Hibernate: create sequence comment_seq start with 1 increment by 10
Hibernate: create table Comment (id bigint not null, text varchar(255), primary key (id))
Hibernate: create table Post (id bigint generated by default as identity, title varchar(255), primary key (id))
Hibernate: create table Tag (id uuid not null, name varchar(255), primary key (id))
Hibernate: insert into Post (title,id) values (?,default)
id статьи: 1
Hibernate: select next value for comment_seq
id комментария: 1
Hibernate: select next value for comment_seq
id комментария: 2
id комментария: 3
id тега: 0807f8ef-7f04-4dfe-b207-c3e9f3ff3bfa
Hibernate: insert into Comment (text,id) values (?,?)
Hibernate: insert into Comment (text,id) values (?,?)
Hibernate: insert into Comment (text,id) values (?,?)
Hibernate: insert into Tag (name,id) values (?,?)Что показывает вывод
- IDENTITY: id становится известен только после вставки строки, поэтому Hibernate выполняет
insertсразу приpersist. Из-за этого он не может объединять вставки в пакеты. - SEQUENCE: Hibernate берёт значения из последовательности заранее. С
allocationSize = 10одно обращение к базе резервирует десять id, поэтому третий комментарий получил id без всякого запроса. Командыinsertждут фиксации. По умолчаниюallocationSizeравен 50 — это и естьincrement by 50, который вы видели раньше. - UUID: значение создаётся в Java, база в этом не участвует.
Для новых проектов последовательность по умолчанию — хороший выбор. UUID полезен, когда id создаются в нескольких местах или их нельзя угадывать.
Натуральные идентификаторы
Помимо суррогатного ключа у строки часто есть уникальный ключ из предметной области — например, адресная часть статьи (slug). Hibernate называет его натуральным идентификатором:
// src/main/java/com/example/blog/Post.java
@Id
@GeneratedValue
private Long id;
@NaturalId
private String slug;@NaturalId импортируется из org.hibernate.annotations и добавляет ограничение уникальности. По нему можно загрузить статью:
// src/main/java/com/example/blog/Main.java
Post post = emf.callInTransaction(em ->
em.find(Post.class, "hello-hibernate", KeyType.NATURAL));Запрос будет таким: select ... from Post p1_0 where p1_0.slug=?. KeyType находится в пакете org.hibernate.
Частая ошибка
Если у сущности есть @GeneratedValue, не задавайте id сами. Hibernate видит заполненный id и решает, что объект уже есть в базе:
Exception in thread "main" jakarta.persistence.EntityExistsException: Detached entity passed to persist: com.example.blog.PostЛибо доверьте генерацию Hibernate, либо уберите @GeneratedValue и всегда назначайте id сами.