— 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:
El generador concreto a utilizar y sus propiedades se puede definir en el atributo generator.
Para definir generadores tenemos las etiquetas:
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:
@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:
@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
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.