28. Для чего нужна аннотация @ID? Какие GeneratedValue вы знаете?
@Id
Аннотация @Id определяет простой (не составной) первичный ключ, состоящий из одного поля. В соответствии с JPA, допустимые типы атрибутов для первичного ключа:
1. примитивные типы и их обертки;
2. строки;
3. BigDecimal и BigInteger;
4. java.util.Date и java.sql.Date.
Стратегии генерации Id
Если мы хотим, чтобы значение первичного ключа генерировалось для нас автоматически, мы можем добавить первичному ключу, отмеченному аннотацией @Id, аннотацию @GeneratedValue. Согласно спецификации JPA возможно 4 различных варианта: AUTO, IDENTITY, SEQUENCE, TABLE. Если мы не укажем значение явно, типом генерации по умолчанию будет AUTO. Спецификация JPA строго не определяет поведение этих стратегий.
@Id
@GeneratedValue(strategy=TABLE, generator="CUST_GEN")
@Column(name="CUST_ID")
Long id;
AUTO
Указывает, что Hibernate должен выбрать подходящую стратегию для конкретной базы данных, учитывая её диалект, так как у разных БД разные способы по умолчанию.
То, как провайдер должен реализовывать тип генерации AUTO, оставлено на усмотрение провайдера. Поведение по умолчанию - исходить из типа поля идентификатора. С версии Hibernate 5.0 для числовых значений генерация основана на SEQUENCE, и, если БД её не поддерживает, то на TABLE.
Пример AUTO, в котором значения первичного ключа будут уникальными на уровне базы данных:
@Entity
public class Student {
@Id
@GeneratedValue
private long studentId;
// ...
}
Интересная особенность, представленная в Hibernate 5, это UUIDGenerator.
Чтобы его использовать, всё, что нам нужно сделать, это объявить идентификатор типа UUID с аннотацией @GeneratedValue:
@Entity
public class Cvourse {
@Id
@GeneratedValue
private UUID courseId;
// ...
}
Hibernate сгенерирует идентификатор вида «8dd5f315-9788-4d00-87bb-10eed9eff566».
IDENTITY
Указывает, что для генерации значения первичного ключа будет использоваться столбец IDENTITY, имеющийся в базе данных. Значения в столбце автоматически увеличиваются, что позволяет базе данных генерировать новое значение при каждой операции вставки. С точки зрения базы данных это очень эффективно, поскольку столбцы с автоинкрементом хорошо оптимизированы и не требуют каких-либо дополнительных операторов. Процесс инкремента (получения следующего) первичного ключа происходит вне текущей выполняемой транзакции, поэтому откат транзакции может в конечном итоге обнулить уже присвоенные значения (могут возникнуть пропуски значений).
Если мы используем Hibernate, то использование IDENTITY имеет существенный недостаток. Так как Hibernate нужен первичный ключ для работы с managed-объектом в persistence context, а мы не можем узнать значение первичного ключа до выполнения инструкции INSERT, то Hibernate должен немедленно выполнить оператор INSERT, чтобы получить этот самый первичный ключ, сгенерированный БД. Только после этого у Hibernate будет возможность работать с сущностью в контексте персистентности, после чего выполнить операцию persist. Но Hibernate, в соответствии со своей идеологией, использует стратегию “транзакционная запись-после” (transactional write-behind), согласно которой он пытается максимально отложить сброс данных в БД из контекста персистентности, чтобы не делать много обращений к БД. Так как поведение при IDENTITY противоречит идеологии и стратегии “транзакционная запись-после”, Hibernate отключает пакетные вставки (batching inserts) для объектов, использующих генератор IDENTITY. Однако, пакетные обновления и удаления (batching updates и batching deletes) всё же поддерживаются. IDENTITY является самым простым в использовании типом генерации, но не самым лучшим с точки зрения производительности. Как уже упоминалось, стратегия генератора первичных ключей IDENTITY не работает при TABLE PER CLASS, поскольку может быть несколько объектов подкласса, имеющих один и тот же идентификатор, и запрос базового класса приведет к получению объектов с одним и тем же идентификатором (даже если они принадлежат разным типам).
SEQUENCE
Указывает, что для получения значений первичного ключа Hibernate должен использовать имеющиеся в базе данных механизмы генерации последовательных значений (Sequence). Но если наша БД не поддерживает тип SEQUENCE, то Hibernate автоматически переключится на тип TABLE.
SEQUENCE - это объект базы данных, который генерирует инкрементные целые числа при каждом последующем запросе. SEQUENCE намного более гибкий, чем IDENTITY, потому что:
❖ SEQUENCE не содержит таблиц, и одну и ту же последовательность можно назначить нескольким столбцам или таблицам;
❖ SEQUENCE может предварительно распределять значения для улучшения производительности;
❖ SEQUENCE может определять шаг инкремента, что позволяет нам воспользоваться «объединенным» алгоритмом Hilo;
❖ SEQUENCE не ограничивает пакетные вставки JDBC в Hibernate;
❖ SEQUENCE не ограничивает модели наследования Hibernate.
При SEQUENCE для получения следующего значения из последовательности базы данных требуются дополнительные операторы select, но это не влияет на производительность для большинства приложений. И если нашему приложению необходимо сохранить огромное количество новых сущностей, мы можем использовать некоторые специфичные для Hibernate оптимизации, чтобы уменьшить количество операторов.
Для работы с этой стратегией Hibernate использует свой класс SequenceStyleGenerator.
SEQUENCE - это тип генерации, рекомендуемый документацией Hibernate. Самый простой способ - просто задать безымянную генерацию последовательности:
@Entity(name = "Product")
public static class Product {
@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE // Имя последовательности не
// определено, поэтому Hibernate будет использовать последовательность
// hibernate_sequence для всех сущностей
)
private Long id;
@Column(name = "product_name")
private String name;
//Getters and setters are omitted for brevity
}
Для всех сущностей с безымянной последовательностью Hibernate будет использовать одну и ту же hibernate_sequence, из которой будет брать для них айдишники.
Используя аннотацию @SequenceGenerator, мы можем указать конкретное имя последовательности для таблицы, а также иные параметры. Также мы можем настроить под себя несколько разных последовательностей (SEQUENCE-генераторов), указав, например, имя последовательности и начальное значение:
@Entity
public class User {
@Id
@GeneratedValue(generator = "sequence-generator")
@GenericGenerator(
name = "sequence-generator",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
@Parameter(name = "sequence_name", value = "user_sequence"),
@Parameter(name = "initial_value", value = "4"),
@Parameter(name = "increment_size", value = "1")
}
)
private long userId;
// ...
}
В этом примере мы установили имя последовательности и начальное значение, что означает, что генерация первичного ключа начнется с 4. Для каждой последовательности сгенерированные значения являются уникальными.
Так, мы можем назначать разные последовательности разным сущностям и они будут брать айдишники из этой последовательности. В зависимости от требований приложения, можно иметь один генератор на всё приложение, по генератору на каждую сущность или несколько генераторов, которыми пользуются несколько сущностей.
Например, у нас есть 10 сущностей, для трех из них мы создадим последовательность с именем first_sequence, из которой они будут брать айдишники.
Для пяти других сущностей создадим последовательность с именем second_sequence, из которой они будут брать свои айдишники. А для оставшихся двух сущностей можем задать безымянную последовательность, и в этом случае айдишники для них будут браться по умолчанию из hibernate_sequence.
TABLE
В настоящее время GenerationType.TABLE используется редко. Hibernate должен получать первичные ключи для сущностей из специальной создаваемой для этих целей таблицы, способной содержать несколько именованных сегментов значений для любого количества сущностей. Основная идея заключается в том, что данная таблица (например, hibernate_sequence) может содержать несколько сегментов со значениями идентификаторов для разных сущностей. Это требует использования пессимистических блокировок, которые помещают все транзакции по получению идентификаторов в очередь. Разумеется, это замедляет работу приложения.
Третья стратегия, GenerationType.TABLE, не зависит от поддержки конкретной базой данных и хранит счётчики значений в отдельной таблице. С одной стороны это более гибкое и настраиваемое решение, с другой стороны более медленное и требующее большей настройки. Вначале требуется создать (вручную!) и проинициализировать (!) таблицу для значений ключей.
Затем создать генератор и связать его со идентификатором:
Используя аннотацию @TableGenerator мы можем настроить этот тип генерации:
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "table-generator")
@TableGenerator(name = "table-generator",
table = "dep_ids",
pkColumnName = "seq_id",
valueColumnName = "seq_value")
private long depId;
// ...
}
Предыдущий вопрос: 27.Как смаппить составной ключ?
Следующий вопрос: 29. Расскажите про аннотации @JoinColumn, @JoinColumns и @JoinTable. Где и для чего они используются?
Все вопросы по теме: список
Все темы: список
Вопросы/замечания/предложения/нашли ошибку: напишите мне