Generar valores para las claves primarias

PEDRO DELGADO YARZA 2014/01/29 13:50

En este apartado vamos a ver qué formas tiene JPA de asignar automáticamente valores a las claves primarias de un objeto, al ser creados, sin necesidad de programar la asignación del siguiente “id” que esté libre.

Para generar los valores de una clave primaria, es decir, que los genere JPA automáticamente, podemos utilizar la anotación @GeneratedValue. Esta anotación tiene definido cuatro tipos de generaciones, que se indican en el atributo strategy:

  • AUTO: La opción por defecto. Deja al proveedor de persistencia elegir cual de las siguientes tres opciones va a utilizar.
  • SEQUENCE: Utiliza una secuencia SQL para obtener el siguiente valor de la clave primaria.
  • TABLE: Necesita una tabla con dos columnas, el nombre de la secuencia y su valor.
  • IDENTITY: utiliza un generador de identidad como las columnas definidas con auto_increment en MySQL.

El generador concreto a utilizar y sus propiedades se puede definir en el atributo generator.

Para definir generadores tenemos las etiquetas:

  • @SequenceGenerator: Usa una secuencia.
  • @TableGenerator: Usa una tabla, contador del ultimo id-
  • @GenericGenerator: (Usado por Hibernate), permite personalizar la generación, tiene los siguientes atributos:
    • name: Define el nombre del generador referenciado en @GeneratedValue.
    • strategy: Define más tipos de generadores definidos por Hibernate como trigger, hilo,system-uuid…o definir unos propios si se pone el nombre de la clase concreta que implementa el generador.

Para este ejemplo usaremos las anotaciones: @GeneratedValue común a todos los generadores y @SequenceGenerator en la que se declara el generador de secuencia con un nombre y se le asocia el nombre de una secuencia “real” de base de datos existente

Con esas dos anotaciones, el motor de persistencia, cada vez que vaya a insertar un nuevo elemento, sabrá que tiene que ir a la base de datos y asociarle el siguiente valor de la secuencia al campo @Id de la clase, al estar anotado con @GeneratedValue.

La anotación @SequenceGenerator tiene las siguientes propiedades:

  • name: Nombre del generador de la clave.
  • sequenceName: Nombre de la secuencia de la base de datos del que se va a obtener la clave.
  • initialValue: Valor inicial de la secuencia.
  • allocationSize: Cantidad a incrementar de la secuencia cuando se arranca la aplicación.
@Entity
@Table(name = "PETICION", schema="TABLON")
@SequenceGenerator(name="generadorIdentificadorPet", sequenceName="PET_GENERADOR_ID" , initialValue=1, allocationSize=1)
public class Peticion {
 
   // Identificador de la petición
   private Long petId;
 
   ...
 
   //Getters Y Setters
 
   @Id
   @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="generadorIdentificadorPet")
   @Column(name = "PET_ID", nullable=false, precision=19, scale = 0)
   @NotNull
   public Long getPetId() {
      return petId;
   }
 
   /**
    * Establecer el identificador de una peticion
    * @param petId el identificador de la peticion
    */	
   public void setPetId(Long petId) {
      this.petId = petId;
   }
 
}

OJO:LIMITACIONES Como se explica en este JIRA, si la clave a la que se le asigna la secuencia es un BigDecimal aparecerá el error:

org.hibernate.id.IdentifierGenerationException: this id generator generates long, integer, short or string

Esto es lógico ya que no tiene sentido tener un campo numérico con decimales como secuencia. En ese caso, lo correcto sería cambiarle el tipo de BigDecimal a Long al campo de Bean de Entidad.

Al intentar incluir un generador en una clave compuesta, por defecto, las anotaciones no funcionan, si queremos incluir un generador como parte de una clave compuesta debemos sealizar los siguientes pasos:

  • Utilizar la clase SequenceCompositeIdGenerator de la siguiente manera (se declara como un @GenericGenerator y se le referencia en la anotacion @GeneratedValue en el campo anotado como @EmbeddedId en el bean de entidad padre.
@GenericGenerator(name = "generadorEntryId", strategy = SequenceCompositeIdGenerator.STRATEGY, parameters = {
                    @org.hibernate.annotations.Parameter(name = SequenceCompositeIdGenerator.SEQUENCE, value = "SEC_FEED_ENTRY"),
                    @org.hibernate.annotations.Parameter(name = SequenceCompositeIdGenerator.CAMPO_ID, value = "id") }
)
public class Entry ........
 
............
@EmbeddedId
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generadorEntryId")
public EntryId getId() {
	return this.id;
}

La etiqueta GenericGenerator le pasamos dos parámetros un SEQUENCE con el nombre de la secuencia y otro el campo, dentro del EntryId al que queremos asignar la secuencia. En nuestro caso EntryId tendra un atributo llamado id, que es el que cada vez que se inserte se le asocie el siguiente valor de la secuencia.

Para mapear con hibernate una generación de ids, que está hecha con un trigger de base de datos, que se dispara al hacer cualquier insert de una fila en la tabla. El trigger en base de datos será del tipo: BEFORE INSERT. Como en el caso de las secuencias, NO se aceptan BigDecimals.

Tenemos dos opciones:

  • Trigger en una tabla con clave unica, a parte de la clave primaria: podemos utilizar las anotaciones de hibernate.
  • Trigger en una tabla sin clave unica, a parte de la clave primaria: utilizaremos un generador propio.

Trigger en una tabla con clave unica, a parte de la clave primaria

Si tenemos una tabla por ejemplo ANUNCIO, con un trigger asociado al insert que actualiza un campo ID que es la clave primaria y que además tiene un campo unico llamado NOMBRE, podríamos utilizar la anotacion @GenericGenerator con el atributo strategy= select para tal efecto. Sólo habría que pasarle el valor del campo UNIQUE a través de @Params.

Esto permitirá a Hibernate, “reobtener” el objeto una vez insertado, a través de otro campo distinto del ID, ya que el ID habrá sido “sobreescrito” por el trigger de base de datos. El código quedaría de la forma:

@GeneratedValue(generator = "trigger")
@GenericGenerator(name = "trigger", strategy = "select", parameters = {
	@Parameter(name = "key", value = "nombre")
    })

Trigger en una tabla con clave primaria compuesta

Si no existe esa campo unico, podemos hacer uso de la clase TriggerAssignedIdentityGenerator de la siguiente manera:

@GeneratedValue(generator = "triggerAssigned")  
@GenericGenerator(name="triggerAssigned", 
                  strategy = TriggerAssignedIdentityGenerator.STRATEGY
)

Para que no nos de un error de “detached entity passed to persist”, el campo Id, debe estar nulo lo que implica quitar la anotacion @NotNull.

  • fdw2.0/fundeweb2.0/gt/generar_valores_para_las_claves_primarias.txt
  • Última modificación: 03/12/2021 09:44
  • por JUAN MIGUEL BERNAL GONZALEZ