1. Introducción
Los identificadores en Hibernate representan la clave principal de una entidad. Esto implica que los valores son únicos para que puedan identificar una entidad específica, que no sean nulos y que no se modifiquen.
Hibernate proporciona algunas formas diferentes de definir identificadores. En este artículo, revisaremos cada método de mapeo de identificadores de entidad usando la biblioteca.
2. Identificadores simples
La forma más sencilla de definir un identificador es mediante la anotación @Id .
Los identificadores simples se asignan mediante @Id a una única propiedad de uno de estos tipos: tipos de envoltura primitiva y primitiva de Java, String, Date, BigDecimal, BigInteger.
Veamos un ejemplo rápido de cómo definir una entidad con una clave principal de tipo long:
@Entity public class Student { @Id private long studentId; // standard constructor, getters, setters }
3. Identificadores generados
Si queremos que el valor de la clave principal se genere automáticamente para nosotros, podemos agregar la anotación @GeneratedValue .
Puede utilizar 4 tipos de generación: AUTO, IDENTITY, SEQUENCE, TABLE.
Si no especificamos un valor explícitamente, el tipo de generación predeterminado es AUTO.
3.1. Generación AUTO
Si usamos el tipo de generación predeterminado, el proveedor de persistencia determinará los valores en función del tipo de atributo de clave principal. Este tipo puede ser numérico o UUID.
Para valores numéricos, la generación se basa en un generador de secuencia o tabla, mientras que los valores de UUID usarán el UUIDGenerator.
Veamos un ejemplo de mapeo de una clave primaria de entidad usando la estrategia de generación AUTO:
@Entity public class Student { @Id @GeneratedValue private long studentId; // ... }
En este caso, los valores de la clave principal serán únicos en el nivel de la base de datos.
Una característica interesante introducida en Hibernate 5 es UUIDGenerator. Para usar esto, todo lo que tenemos que hacer es declarar una identificación de tipo UUID con la anotación @GeneratedValue :
@Entity public class Course { @Id @GeneratedValue private UUID courseId; // ... }
Hibernate generará una identificación de la forma "8dd5f315-9788-4d00-87bb-10eed9eff566".
3.2. Generación de IDENTIDAD
Este tipo de generación se basa en IdentityGenerator, que espera valores generados por una columna de identidad en la base de datos, lo que significa que se incrementan automáticamente.
Para utilizar este tipo de generación, solo necesitamos establecer el parámetro de estrategia :
@Entity public class Student { @Id @GeneratedValue (strategy = GenerationType.IDENTITY) private long studentId; // ... }
Una cosa a tener en cuenta es que la generación de IDENTIDAD deshabilita las actualizaciones por lotes.
3.3. Generación de SECUENCIA
Para usar una identificación basada en secuencia, Hibernate proporciona la clase SequenceStyleGenerator .
Este generador usa secuencias si son compatibles con nuestra base de datos y cambia a la generación de tablas si no lo son.
Para personalizar el nombre de la secuencia, podemos usar la anotación @GenericGenerator con la estrategia 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; // ... }
En este ejemplo, también hemos establecido un valor inicial para la secuencia, lo que significa que la generación de la clave principal comenzará en 4.
SEQUENCE es el tipo de generación recomendado por la documentación de Hibernate.
Los valores generados son únicos por secuencia. Si no especifica un nombre de secuencia, Hibernate reutilizará la misma hibernate_sequence para diferentes tipos.
3.4. Generación TABLE
El TableGenerator utiliza una tabla de base de datos subyacente que contiene segmentos de identificador de valores generación.
Personalicemos el nombre de la tabla usando la anotación @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; // ... }
En este ejemplo, podemos ver que otros atributos como pkColumnName y valueColumnName también se pueden personalizar.
La desventaja de este método es que no se escala bien y puede afectar negativamente al rendimiento.
En resumen, estos cuatro tipos de generación darán como resultado la generación de valores similares, pero utilizarán diferentes mecanismos de base de datos.
3.5. Generador personalizado
Si no queremos utilizar ninguna de las estrategias listas para usar, podemos definir nuestro generador personalizado implementando la interfaz IdentifierGenerator .
Creemos un generador que construya identificadores que contengan un prefijo de cadena y un número:
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
En este artículo, hemos visto las múltiples formas en que podemos definir identificadores en Hibernate.
El código fuente completo de los ejemplos se puede encontrar en GitHub.