Преглед на идентификаторите в режим на хибернация / JPA

1. Въведение

Идентификаторите в хибернация представляват първичния ключ на даден обект. Това означава, че стойностите са уникални, така че да могат да идентифицират конкретен обект, да не са нула и да не бъдат модифицирани.

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, IDENTITY, SEQUENCE, TABLE.

Ако не посочим изрично стойност, типът на генериране по подразбиране е AUTO.

3.1. АВТОМАТИЧНО поколение

Ако използваме типа на генериране по подразбиране, доставчикът на постоянство ще определи стойности въз основа на типа на атрибута на първичния ключ. Този тип може да бъде цифров или UUID.

За числови стойности генерирането се основава на генератор на последователност или таблица, докато стойностите на UUID ще използват UUIDGenerator.

Нека видим пример за картографиране на първичен ключ на обект с помощта на стратегия за автоматично генериране:

@Entity public class Student { @Id @GeneratedValue private long studentId; // ... }

В този случай стойностите на първичния ключ ще бъдат уникални на ниво база данни.

Интересна функция, представена в Hibernate 5, е UUIDGenerator. За да използваме това, всичко, което трябва да направим, е да декларираме идентификатор от тип UUID с анотация @GeneratedValue :

@Entity public class Course { @Id @GeneratedValue private UUID courseId; // ... }

Hibernate ще генерира идентификационен номер от формата „8dd5f315-9788-4d00-87bb-10eed9eff566“.

3.2. ИДЕНТИЧНОСТ Поколение

Този тип генериране разчита на IdentityGenerator, който очаква стойности, генерирани от колона за идентичност в базата данни, което означава, че те се увеличават автоматично.

За да използваме този тип поколение, трябва само да зададем параметъра на стратегията :

@Entity public class Student { @Id @GeneratedValue (strategy = GenerationType.IDENTITY) private long studentId; // ... }

Едно нещо, което трябва да се отбележи е, че генерирането на IDENTITY деактивира пакетните актуализации.

3.3. ПОСЛЕДОВАТЕЛНОСТ Generation

За да използва идентификатор, базиран на последователност, 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.

SEQUENCE е генерираният тип, препоръчан от документацията за хибернация.

Генерираните стойности са уникални за всяка последователност. Ако не посочите име на последователност, Hibernate ще използва повторно една и съща hibernate_sequence за различни типове.

3.4. ТАБЛИЦА Поколение

В TableGenerator използва основната таблица на база данни, която държи сегменти на ценности идентификатор поколение.

Нека персонализираме името на таблицата, като използваме анотацията @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"); } }

In this example, we override the generate() method from the IdentifierGenerator interface and first find the highest number from the existing primary keys of the form prefix-XX.

Then we add 1 to the maximum number found and append the prefix property to obtain the newly generated id value.

Our class also implements the Configurable interface, so that we can set the prefix property value in the configure() method.

Next, let's add this custom generator to an entity. For this, we can use the @GenericGenerator annotation with a strategy parameter that contains the full class name of our generator class:

@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; // ... }

Also, notice we've set the prefix parameter to “prod”.

Let's see a quick JUnit test for a clearer understanding of the id values generated:

@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"); }

Here, the first value generated using the “prod” prefix was “prod-1”, followed by “prod-2”.

4. Composite Identifiers

Besides the simple identifiers we've seen so far, Hibernate also allows us to define composite identifiers.

A composite id is represented by a primary key class with one or more persistent attributes.

The primary key class must fulfill several conditions:

  • it should be defined using @EmbeddedId or @IdClass annotations
  • it should be public, serializable and have a public no-arg constructor
  • it should implement equals() and hashCode() methods

The class's attributes can be basic, composite or ManyToOne while avoiding collections and OneToOne attributes.

4.1. @EmbeddedId

To define an id using @EmbeddedId, first we need a primary key class annotated with @Embeddable:

@Embeddable public class OrderEntryPK implements Serializable { private long orderId; private long productId; // standard constructor, getters, setters // equals() and hashCode() }

Next, we can add an id of type OrderEntryPK to an entity using @EmbeddedId:

@Entity public class OrderEntry { @EmbeddedId private OrderEntryPK entryId; // ... }

Let's see how we can use this type of composite id to set the primary key for an entity:

@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); }

Here the OrderEntry object has an OrderEntryPK primary id formed of two attributes: orderId and productId.

4.2. @IdClass

The @IdClass annotation is similar to the @EmbeddedId, except the attributes are defined in the main entity class using @Id for each one.

The primary-key class will look the same as before.

Let's rewrite the OrderEntry example with an @IdClass:

@Entity @IdClass(OrderEntryPK.class) public class OrderEntry { @Id private long orderId; @Id private long productId; // ... }

Then we can set the id values directly on the OrderEntry object:

@Test public void whenSaveIdClassEntity_thenOk() { OrderEntry entry = new OrderEntry(); entry.setOrderId(1L); entry.setProductId(30L); session.save(entry); assertThat(entry.getOrderId()).isEqualTo(1L); }

Note that for both types of composite ids, the primary key class can also contain @ManyToOne attributes.

Hibernate also allows defining primary-keys made up of @ManyToOne associations combined with @Id annotation. In this case, the entity class should also fulfill the conditions of a primary-key class.

The disadvantage of this method is that there's no separation between the entity object and the identifier.

5. Derived Identifiers

Derived identifiers are obtained from an entity's association using the @MapsId annotation.

First, let's create a UserProfile entity which derives its id from a one-to-one association with the User entity:

@Entity public class UserProfile { @Id private long profileId; @OneToOne @MapsId private User user; // ... }

Next, let's verify that a UserProfile instance has the same id as its associated User instance:

@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. Conclusion

В тази статия видяхме многобройните начини, по които можем да дефинираме идентификатори в хибернация.

Пълният изходен код на примерите може да бъде намерен в GitHub.