1. введение
Идентификаторы в Hibernate представляют собой первичный ключ сущности. Это означает, что значения уникальны, чтобы они могли идентифицировать конкретную сущность, что они не являются нулевыми и что они не будут изменены.
Hibernate предоставляет несколько различных способов определения идентификаторов. В этой статье мы рассмотрим каждый метод сопоставления идентификаторов сущностей с помощью библиотеки.
2. Простые Идентификаторы
Самый простой способ определить идентификатор-это использовать аннотацию @Id .
Простые идентификаторы сопоставляются с помощью @Id с одним свойством одного из этих типов: примитивы Java и примитивные типы оболочек, String, Date, BigDecimal, BigInteger.
Давайте рассмотрим краткий пример определения сущности с первичным ключом типа long:
@Entity public class Student { @Id private long studentId; // standard constructor, getters, setters }
3. Сгенерированные Идентификаторы
Если мы хотим, чтобы значение первичного ключа создавалось автоматически для нас, мы можем добавить @GeneratedValue аннотацию .
Это может использовать 4 типа генерации: АВТО, ИДЕНТИФИКАЦИЯ, ПОСЛЕДОВАТЕЛЬНОСТЬ, ТАБЛИЦА.
Если мы не указываем значение явно, тип генерации по умолчанию равен AUTO.
3.1. Автогенерация
Если мы используем тип генерации по умолчанию, поставщик сохраняемости будет определять значения на основе типа атрибута первичного ключа. Этот тип может быть числовым или UUID.
Для числовых значений генерация основана на генераторе последовательностей или таблиц, в то время как UUID значения будут использовать UUIDGenerator.
Давайте рассмотрим пример сопоставления первичного ключа сущности с использованием стратегии автоматической генерации:
@Entity public class Student { @Id @GeneratedValue private long studentId; // ... }
В этом случае значения первичного ключа будут уникальными на уровне базы данных.
Интересной функцией, введенной в Hibernate 5, является генератор UUID. Чтобы использовать это, все, что нам нужно сделать, это объявить идентификатор типа UUID с @GeneratedValue аннотацией:
@Entity public class Course { @Id @GeneratedValue private UUID courseId; // ... }
Hibernate сгенерирует идентификатор формы “8dd5f315-9788-4d00-87bb-10eed9eff566”.
3.2. Формирование идентичности
Этот тип генерации зависит от генератора Identity , который ожидает значения, генерируемые столбцом identity в базе данных, что означает, что они автоматически увеличиваются.
Чтобы использовать этот тип генерации, нам нужно только задать параметр strategy :
@Entity public class Student { @Id @GeneratedValue (strategy = GenerationType.IDENTITY) private long studentId; // ... }
Следует отметить, что генерация удостоверений отключает пакетное обновление.
3.3. Генерация последовательностей
Чтобы использовать идентификатор на основе последовательности, Hibernate предоставляет класс SequenceStyleGenerator .
Этот генератор использует последовательности, если они поддерживаются нашей базой данных, и переключается на генерацию таблиц, если это не так.
Чтобы настроить имя последовательности, мы можем использовать @GenericGenerator аннотацию со стратегией SequenceStyleGenerator:
@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.
ПОСЛЕДОВАТЕЛЬНОСТЬ – это тип генерации, рекомендуемый документацией Hibernate.
Сгенерированные значения уникальны для каждой последовательности. Если вы не укажете имя последовательности, Hibernate будет повторно использовать один и тот же hibernate_sequence для разных типов.
3.4. Генерация ТАБЛИЦ
Генератор таблиц использует базовую таблицу базы данных, которая содержит сегменты значений генерации идентификаторов.
Давайте настроим имя таблицы с помощью аннотации @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; // ... }
В этом примере мы видим, что другие атрибуты, такие как pkColumnName и valueColumnName , также могут быть настроены.
Недостатком этого метода является то, что он плохо масштабируется и может негативно повлиять на производительность.
Подводя итог, можно сказать, что эти четыре типа генерации приведут к созданию одинаковых значений, но с использованием различных механизмов базы данных.
3.5. Пользовательский генератор
Если мы не хотим использовать ни одну из готовых стратегий, мы можем определить наш пользовательский генератор, реализовав интерфейс IdentifierGenerator |.
Давайте создадим генератор, который строит идентификаторы, содержащие префикс String и число:
public class MyGenerator implements IdentifierGenerator, Configurable { private String prefix; @Override public Serializable generate( SharedSessionContractImplementor session, Object obj) throws HibernateException { String query = String.format("select %s from %s", session.getEntityPersister(obj.getClass().getName(), obj) .getIdentifierPropertyName(), obj.getClass().getSimpleName()); Stream ids = session.createQuery(query).stream(); Long max = ids.map(o -> o.replace(prefix + "-", "")) .mapToLong(Long::parseLong) .max() .orElse(0L); return prefix + "-" + (max + 1); } @Override public void configure(Type type, Properties properties, ServiceRegistry serviceRegistry) throws MappingException { prefix = properties.getProperty("prefix"); } }
В этом примере мы переопределяем метод generate() из IdentifierGenerator interface и сначала находим наибольшее число из существующих первичных ключей формы prefix-XX.
Затем мы добавляем 1 к максимальному найденному числу и добавляем свойство prefix , чтобы получить вновь сгенерированное значение идентификатора.
Наш класс также реализует интерфейс Конфигурируемый , так что мы можем задать значение свойства prefix в методе configure () .
Далее, давайте добавим этот пользовательский генератор в сущность. Для этого мы можем использовать аннотацию @GenericGenerator с параметром strategy , который содержит полное имя класса нашего класса генератора :
@Entity public class Product { @Id @GeneratedValue(generator = "prod-generator") @GenericGenerator(name = "prod-generator", parameters = @Parameter(name = "prefix", value = "prod"), strategy = "com.baeldung.hibernate.pojo.generator.MyGenerator") private String prodId; // ... }
Кроме того, обратите внимание, что мы установили для параметра prefix значение “prod”.
Давайте посмотрим быстрый тест JUnit для более четкого понимания сгенерированных значений идентификаторов:
@Test public void whenSaveCustomGeneratedId_thenOk() { Product product = new Product(); session.save(product); Product product2 = new Product(); session.save(product2); assertThat(product2.getProdId()).isEqualTo("prod-2"); }
Здесь первое значение, сгенерированное с использованием префикса “prod”, было “prod-1”, за которым следовал “prof-2”.
4. Составные Идентификаторы
Помимо простых идентификаторов, которые мы видели до сих пор, Hibernate также позволяет нам определять составные идентификаторы.
Составной идентификатор представлен классом первичного ключа с одним или несколькими постоянными атрибутами.
Класс первичного ключа должен выполнять несколько условий:
- он должен быть определен с помощью @EmbeddedId или @IdClass аннотаций
- он должен быть общедоступным, сериализуемым и иметь общедоступный конструктор no-arg
- он должен реализовать equals() и hashCode() методы
Атрибуты класса могут быть базовыми, составными или ManyToOne, избегая при этом коллекций и OneToOne атрибутов.
4.1. @EmbeddedId
Чтобы определить идентификатор с помощью @EmbeddedId, сначала нам нужен класс первичного ключа с аннотацией @Embeddable:
@Embeddable public class OrderEntryPK implements Serializable { private long orderId; private long productId; // standard constructor, getters, setters // equals() and hashCode() }
Затем мы можем добавить идентификатор типа Order Entry PK к сущности с помощью @ EmbeddedId :
@Entity public class OrderEntry { @EmbeddedId private OrderEntryPK entryId; // ... }
Давайте посмотрим, как мы можем использовать этот тип составного идентификатора для установки первичного ключа для сущности:
@Test public void whenSaveCompositeIdEntity_thenOk() { OrderEntryPK entryPK = new OrderEntryPK(); entryPK.setOrderId(1L); entryPK.setProductId(30L); OrderEntry entry = new OrderEntry(); entry.setEntryId(entryPK); session.save(entry); assertThat(entry.getEntryId().getOrderId()).isEqualTo(1L); }
Здесь объект Order Entry имеет Order Entry PK первичный идентификатор, сформированный из двух атрибутов: OrderID и ProductID.
4.2. @IdClass
Аннотация @IdClass аналогична аннотации @EmbeddedId, за исключением того, что атрибуты определяются в основном классе сущностей с использованием @Id для каждого из них.
Класс первичного ключа будет выглядеть так же, как и раньше.
Давайте перепишем Запись заказа пример с @IdClass:
@Entity @IdClass(OrderEntryPK.class) public class OrderEntry { @Id private long orderId; @Id private long productId; // ... }
Затем мы можем установить значения идентификаторов непосредственно в Записи заказа объекте:
@Test public void whenSaveIdClassEntity_thenOk() { OrderEntry entry = new OrderEntry(); entry.setOrderId(1L); entry.setProductId(30L); session.save(entry); assertThat(entry.getOrderId()).isEqualTo(1L); }
Обратите внимание, что для обоих типов составных идентификаторов класс первичного ключа также может содержать атрибуты @ManyToOne .
Hibernate также позволяет определять первичные ключи, состоящие из @ManyToOne ассоциаций в сочетании с @Id аннотацией. В этом случае класс сущности также должен выполнять условия класса первичного ключа.
Недостатком этого метода является отсутствие разделения между объектом сущности и идентификатором.
5. Производные Идентификаторы
Производные идентификаторы получаются из ассоциации сущности с помощью аннотации @MapsId .
Во-первых, давайте создадим сущность UserProfile , которая получает свой идентификатор из однозначной связи с сущностью User :
@Entity public class UserProfile { @Id private long profileId; @OneToOne @MapsId private User user; // ... }
Затем давайте проверим, что экземпляр UserProfile имеет тот же идентификатор, что и связанный с ним экземпляр User :
@Test public void whenSaveDerivedIdEntity_thenOk() { User user = new User(); session.save(user); UserProfile profile = new UserProfile(); profile.setUser(user); session.save(profile); assertThat(profile.getProfileId()).isEqualTo(user.getUserId()); }
6. Заключение
В этой статье мы рассмотрели несколько способов определения идентификаторов в режиме гибернации.
Полный исходный код примеров можно найти на GitHub .