Una panoramica degli identificatori in Hibernate / JPA

1. Introduzione

Gli identificatori in Hibernate rappresentano la chiave primaria di un'entità. Ciò implica che i valori sono univoci in modo che possano identificare un'entità specifica, che non siano nulli e che non vengano modificati.

Hibernate fornisce diversi modi per definire gli identificatori. In questo articolo, esamineremo ogni metodo di mappatura degli ID entità utilizzando la libreria.

2. Identificatori semplici

Il modo più semplice per definire un identificatore è usare l' annotazione @Id .

Gli ID semplici vengono mappati utilizzando @Id a una singola proprietà di uno di questi tipi: tipi wrapper primitivi e primitivi Java, String, Date, BigDecimal, BigInteger.

Vediamo un rapido esempio di definizione di un'entità con una chiave primaria di tipo long:

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

3. Identificatori generati

Se vogliamo che il valore della chiave primaria venga generato automaticamente per noi, possiamo aggiungere l' annotazione @GeneratedValue .

Questo può utilizzare 4 tipi di generazione: AUTO, IDENTITY, SEQUENCE, TABLE.

Se non specifichiamo un valore in modo esplicito, il tipo di generazione predefinito è AUTO.

3.1. Generazione AUTO

Se stiamo utilizzando il tipo di generazione predefinito, il provider di persistenza determinerà i valori in base al tipo dell'attributo della chiave primaria. Questo tipo può essere numerico o UUID.

Per i valori numerici, la generazione si basa su un generatore di sequenze o tabelle, mentre i valori UUID utilizzeranno UUIDGenerator.

Vediamo un esempio di mappatura di una chiave primaria di entità utilizzando la strategia di generazione AUTO:

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

In questo caso, i valori della chiave primaria saranno univoci a livello di database.

Una caratteristica interessante introdotta in Hibernate 5 è l' UUIDGenerator. Per usarlo, tutto ciò che dobbiamo fare è dichiarare un id di tipo UUID con l' annotazione @GeneratedValue :

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

Hibernate genererà un ID nella forma "8dd5f315-9788-4d00-87bb-10eed9eff566".

3.2. IDENTITY Generation

Questo tipo di generazione si basa su IdentityGenerator che prevede i valori generati da una colonna Identity nel database, ovvero vengono incrementati automaticamente.

Per utilizzare questo tipo di generazione, dobbiamo solo impostare il parametro della strategia :

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

Una cosa da notare è che la generazione di IDENTITY disabilita gli aggiornamenti batch.

3.3. Generazione SEQUENCE

Per utilizzare un ID basato su sequenza, Hibernate fornisce la classe SequenceStyleGenerator .

Questo generatore utilizza sequenze se sono supportate dal nostro database e passa alla generazione di tabelle se non lo sono.

Per personalizzare il nome della sequenza, possiamo utilizzare l' annotazione @GenericGenerator con la strategia 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; // ... }

In questo esempio, abbiamo anche impostato un valore iniziale per la sequenza, il che significa che la generazione della chiave primaria inizierà a 4.

SEQUENCE è il tipo di generazione consigliato dalla documentazione di Hibernate.

I valori generati sono univoci per sequenza. Se non specifichi un nome di sequenza, Hibernate riutilizzerà lo stesso hibernate_sequence per tipi diversi.

3.4. Generazione TABELLA

Il TableGenerator utilizza una tabella di database sottostante che contiene segmenti di identificatori valori generazione.

Personalizziamo il nome della tabella utilizzando l' annotazione @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; // ... }

In questo esempio, possiamo vedere che anche altri attributi come pkColumnName e valueColumnName possono essere personalizzati.

Lo svantaggio di questo metodo è che non si adatta bene e può influire negativamente sulle prestazioni.

Per riassumere, questi quattro tipi di generazione comporteranno la generazione di valori simili ma utilizzeranno meccanismi di database diversi.

3.5. Generatore personalizzato

Se non vogliamo utilizzare nessuna delle strategie out-of-the-box, possiamo definire il nostro generatore personalizzato implementando l' interfaccia IdentifierGenerator .

Creiamo un generatore che costruisca identificatori contenenti un prefisso String e un numero:

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 questo esempio, sovrascrivere la generate () metodo dal IdentifierGenerator interfaccia e primo troviamo il maggior numero dai tasti primarie esistenti della forma prefisso XX.

Quindi aggiungiamo 1 al numero massimo trovato e aggiungiamo la proprietà prefix per ottenere il valore id appena generato.

La nostra classe implementa anche l' interfaccia Configurable , in modo che possiamo impostare il valore della proprietà prefix nel metodo configure () .

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 questo articolo, abbiamo visto i diversi modi in cui possiamo definire gli identificatori in Hibernate.

Il codice sorgente completo degli esempi può essere trovato su GitHub.