Eine Übersicht der Kennungen in Hibernate / JPA

1. Einleitung

Bezeichner im Ruhezustand stellen den Primärschlüssel einer Entität dar. Dies bedeutet, dass die Werte eindeutig sind, damit sie eine bestimmte Entität identifizieren können, dass sie nicht null sind und dass sie nicht geändert werden.

Der Ruhezustand bietet verschiedene Möglichkeiten, um Bezeichner zu definieren. In diesem Artikel werden wir jede Methode zum Zuordnen von Entitäts-IDs mithilfe der Bibliothek überprüfen.

2. Einfache Kennungen

Der einfachste Weg, einen Bezeichner zu definieren, ist die Verwendung der Annotation @Id .

Einfache IDs werden mit @Id einer einzelnen Eigenschaft eines dieser Typen zugeordnet: Java-Primitiv- und Primitiv-Wrapper-Typen, String, Date, BigDecimal, BigInteger.

Sehen wir uns ein kurzes Beispiel für die Definition einer Entität mit einem Primärschlüssel vom Typ long an:

@Entity public class Student { @Id private long studentId; // standard constructor, getters, setters }

3. Generierte Kennungen

Wenn der Primärschlüsselwert automatisch für uns generiert werden soll, können wir die Annotation @GeneratedValue hinzufügen .

Dies kann 4 Generierungstypen verwenden: AUTO, IDENTITY, SEQUENCE, TABLE.

Wenn wir keinen Wert explizit angeben, ist der Generierungstyp standardmäßig AUTO.

3.1. AUTO Generation

Wenn wir den Standardgenerierungstyp verwenden, ermittelt der Persistenzanbieter die Werte basierend auf dem Typ des Primärschlüsselattributs. Dieser Typ kann numerisch oder UUID sein.

Bei numerischen Werten basiert die Generierung auf einem Sequenz- oder Tabellengenerator, während UUID- Werte den UUIDGenerator verwenden.

Sehen wir uns ein Beispiel für die Zuordnung eines Entitätsprimärschlüssels mithilfe der AUTO-Generierungsstrategie an:

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

In diesem Fall sind die Primärschlüsselwerte auf Datenbankebene eindeutig.

Eine interessante Funktion, die in Hibernate 5 eingeführt wurde, ist der UUIDGenerator. Um dies zu verwenden, müssen wir lediglich eine ID vom Typ UUID mit der Annotation @GeneratedValue deklarieren :

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

Im Ruhezustand wird eine ID der Form "8dd5f315-9788-4d00-87bb-10eed9eff566" generiert.

3.2. IDENTITY- Generierung

Diese Art der Generierung basiert auf dem IdentityGenerator, der Werte erwartet, die von einer Identitätsspalte in der Datenbank generiert werden , dh sie werden automatisch inkrementiert.

Um diesen Generierungstyp zu verwenden, müssen wir nur den Strategieparameter festlegen :

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

Zu beachten ist, dass die IDENTITY-Generierung Stapelaktualisierungen deaktiviert.

3.3. SEQUENZ- Erzeugung

Um eine sequenzbasierte ID zu verwenden, stellt Hibernate die SequenceStyleGenerator- Klasse bereit .

Dieser Generator verwendet Sequenzen, wenn sie von unserer Datenbank unterstützt werden, und wechselt zur Tabellengenerierung, wenn dies nicht der Fall ist.

Um den Sequenznamen anzupassen, können Sie die Annotation @GenericGenerator mit der SequenceStyleGenerator-Strategie verwenden:

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

In diesem Beispiel haben wir auch einen Anfangswert für die Sequenz festgelegt, was bedeutet, dass die Primärschlüsselgenerierung bei 4 beginnt.

SEQUENCE ist der in der Hibernate-Dokumentation empfohlene Generierungstyp.

Die generierten Werte sind pro Sequenz eindeutig. Wenn Sie keinen Sequenznamen angeben, verwendet Hibernate dieselbe hibernate_sequence für verschiedene Typen erneut.

3.4. Tabellengenerierung

Der TableGenerator verwendet eine zugrunde liegende Datenbanktabelle, die Segmente von Bezeichnergenerierungswerten enthält.

Passen wir den Tabellennamen mithilfe der Annotation @TableGenerator an :

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

In this example, we can see that other attributes such as the pkColumnName and valueColumnName can also be customized.

The disadvantage of this method is that it doesn't scale well and can negatively affect performance.

To sum up, these four generation types will result in similar values being generated but use different database mechanisms.

3.5. Custom Generator

If we don't want to use any of the out-of-the-box strategies, we can define our custom generator by implementing the IdentifierGenerator interface.

Let's create a generator that builds identifiers containing a String prefix and a number:

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

In diesem Artikel haben wir die verschiedenen Möglichkeiten gesehen, wie wir Bezeichner im Ruhezustand definieren können.

Den vollständigen Quellcode der Beispiele finden Sie auf GitHub.