Antes de explicar las herramientas que posee Hibernate al respecto de las claves primarias pasemos a repasar un poco los conceptos de bases de datos sobre claves primarias.
¿Qué propiedades debe tener una buena clave primaria?
Esta propiedad es obvia para todos , así que no me extenderé más.
Esta propiedad también está clara para cualquiera que sepa de bases de datos
Esta propiedad no es esencial que la cumpla una clave primaria pero si cambia se obliga a modificar todas las filas de otras tablas que tuvieran relación con la clave primaria que se ha modificado. Se podría dar el caso de que fuera necesario modificar miles o millones de filas de una base de datos si cambia una clave primaria, así que es recomendable que una clave primaria nunca cambie.
Esta propiedad tampoco es esencial que la cumpla una clave primaria. Sin embargo en ciertas circunstancias al usar SUBSELECTs en el lenguaje SQL se tiene que recurrir a extensiones propietarias de Oracle por culpa de tener claves primarias compuestas. Además de que podemos encontrarnos con algunas herramientas de desarrollo - como las de generación de informes- que no soporten claves primarias compuestas. Así que para evitar problemas sería recomendable usar claves primarias de una única columna.
Dado que para cada fila que generemos es necesaria una clave primaria es recomendable que el coste de generarla sea lo más bajo posible ya que puede ser un gran coste de tiempo/recursos generar miles o millones de claves primarias diariamente. En los entornos en la nube actuales, en los que se cobra por uso de CPU, la elección de una correcta clave primaria puede ser un ahorro de tiempo y dinero.
¿Cómo elegir entonces una clave primaria que satisfaga todas las anteriores características? La respuesta es sencilla: en la mayoría de los casos la clave primaria debería ser un valor numérico generado automáticamente.
¿Y qué hacemos con las clave primarias naturales? Una clave primaria natural es aquella columna/s de base de datos que actua de clave primaria en el modelo de negocio en el que estamos trabajando.
Por ejemplo, en la Agencia tributaria (Hacienda) debe ser un requerimiento legal que una persona disponga de un NIF para poder tributar. Lo natural es que si tuvieran una tabla Persona
, el NIF fuera la clave primaria de dicha tabla. Esta columna se llamaría la clave primaria natural
ya que es una columna relacionada con el modelo de negocio con el que estamos trabajando.
Pero, ¿qué problemas tiene usar una clave primaria natural como clave primaria?
Por todo ello seguimos recomendando usar una clave primaria que NO sea natural y que NO tenga significado en el modelo de negocio para evitarnos cualquier tipo de problema indeseado en el futuro.
Antes de entrar en cómo se implementa en Hibernate la generación de la clave primaria , veamos las clases Java y las tablas que se usan.
Para nuestro ejemplo vamos a usar la clase:
public class Profesor implements Serializable { private int id; private String nombre; private String ape1; private String ape2; public Profesor(){ } public Profesor(String nombre, String ape1, String ape2) { this.nombre = nombre; this.ape1 = ape1; this.ape2 = ape2; } }
Podemos apreciar en la línea 11 que al constructor ya no se le pasa el valor de la propiedad id
ya que será el propio Hibernate el que la generará.
En el siguiente diagrama UML muestra la clase Profesor
.
Al persistir la clase será necesario un único fichero de persistencia:
Profesor.hbm.xml
El fichero Profesor.hbm.xml
quedará de la siguiente forma:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="ejemplo02.Profesor" > <id column="Id" name="id" type="integer"> <generator class="increment" /> </id> <property name="nombre" /> <property name="ape1" /> <property name="ape2" /> </class> </hibernate-mapping>
El fichero básicamente es muy sencillo excepto por el nuevo tag <generator>
de la línea 6.
El tag <generator>
se utiliza para indicar que la clave primaria será generada por el propio Hibernate en vez de asignarla directamente el usuario:
class
:Este atributo indica el método que usará Hibernate para calcular la clave primaria.En nuestro ejemplo el valor es increment
.
Los valores más usados del atributo class
son los siguientes:
Valor | Descripción |
---|---|
native | Hibernate usará alguno de los siguientes métodos dependiendo de la base de datos. De esta forma si cambiamos de base de datos se seguirá usando la mejor forma de generar la clave primaria |
identity | Hibernate usará el valor de la columna de tipo autoincremento. Es decir, que al insertar la fila, la base de datos le asignará el valor. La columna de base de datos debe ser de tipo autonumérico |
sequence | Se utiliza una secuencia como las que existen en Oracle o PostgreSQL , no es compatible con MySQL. La columna de base de datos debe ser de tipo numérico |
increment | Se lanza una consulta SELECT MAX() contra la columna de la base de datos y se obtiene el valor de la última clave primaria, incrementando el nº en 1.La columna de base de datos debe ser de tipo numérico |
uuid.hex | Hibernate genera un identificador único como un String. Se usa para generar claves primarias únicas entre distintas bases de datos.La columna de base de datos debe ser de tipo alfanumérico. |
guid | Hibernate genera un identificador único como un String pero usando las funciones que provee SQL Server y MySQL. Se usa para generar claves primarias únicas entre distintas bases de datos.La columna de base de datos debe ser de tipo alfanumérico. |
foreign | Se usará el valor de otro objeto como la clave primaria. Un uso de ello es en relaciones uno a uno donde el segundo objeto debe tener la misma clave primaria que el primer objeto a guardar. |
sequence
es necesario indicar el nombre de la secuencia que va a usar Hibernate para obtener el valor. En ese caso el fichero Profesor.hbm.xml habría que modificarlo de la siguiente forma.
<id column="Id" name="id" type="integer"> <generator class="sequence" > <param name="sequence">secuencia_idProfesor</param> </generator> </id>
Vemos que en la línea 3 se ha añadido el tag <param>
para indicar el nombre de la secuencia a usar.En el ejemplo el nombre de la secuencia a usar es secuencia_idProfesor
.
foreign
es necesario indicar la propiedad del otro objeto del que se obtendrá su clave primaria. Profesor
y Direccion
como en el tema Uno a uno (bidireccional) la clave primaria de Direccion
sería la misma que la de Profesor
. En ese caso el xml que defina la clave primaria de Direccion
quedaría de la siguiente forma:
<id column="Id" name="id" type="integer"> <generator class="foreign" > <param name="property">profesor</param> </generator> </id>
Vemos que en la línea 3 se ha añadido el tag <param>
para indicar el nombre de la propiedad de cuyo objeto obtener la clave primaria.En el ejemplo el nombre de la propiedad es profesor
.
guid
o uuid.hex
.increment
ya que la consulta SELECT MAX()
generará muchos bloqueos en entornos de multiples usuarios.identity
o sequence
estará condicionado a la base de datos que usemos ya que lo normal es que tengan secuencias o columnas autoincrementales.native
ya que, aunque aparentemente sea la más portable, siempre hay que hacer cambios en la base de datos y además será necesario conocer qué método va a usar hibernate en cada base de datos.Así que mejor lo indicamos directamente y si cambiamos de base de datos seremos nosotros los que pensaremos cuál es el mejor método a usar. Además ,por ejemplo, si pasamos de MySQL que permite autoincremento a Oracle que necesita secuencias , tendremos que indicar el nombre de la secuencia a usar, así que muy portable no veo el método native
.
Para usar anotaciones deberemos modificar el código fuente de las clases Java y no usar los ficheros .hbm.xml
.
El código fuente de la clase Profesor
queda de la siguiente forma:
@Entity @Table(name="Profesor") public class Profesor implements Serializable { @Id @Column(name="Id") @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; @Column(name="nombre") private String nombre; @Column(name="ape1") private String ape1; @Column(name="ape2") private String ape2; public Profesor(){ } public Profesor(String nombre, String ape1, String ape2) { this.nombre = nombre; this.ape1 = ape1; this.ape2 = ape2; } }
Podemos ver cómo se ha añadido la anotación @GeneratedValue
para la generación de la clave primaria.
GenerationType.IDENTITY
.Los valores más usados para este atributo son los siguientes:Estrategia | Descripción |
---|---|
GenerationType.AUTO | Hibernate usará alguno de los siguientes métodos dependiendo de la base de datos. De esta forma, si cambiamos de base de datos, se seguirá usando la mejor forma de generar la clave primaria. |
GenerationType.IDENTITY | Hibernate usará el valor de la columna de tipo autoincremento. Es decir que al insertar la fila la base de datos le asignará el valor. La columna de base de datos debe ser de tipo autonumérico |
GenerationType.SEQUENCE | Se utiliza una secuencia como las que existen en Oracle o PostgreSQL . No es compatible con MySQL. La columna de base de datos debe ser de tipo numérico |
GenerationType.SEQUENCE
es necesario indicar el nombre de la secuencia que va a usar Hibernate para obtener el valor. En ese caso el código fuente de la clase profesor habría que modificarlo de la siguiente forma.
@Id @Column(name="Id") @GeneratedValue(strategy=GenerationType.SEQUENCE,generator="secuencia_idProfesor") private int id;
Vemos que en la línea 7 se ha añadido el atributo generator
para indicar el nombre de la secuencia a usar.En el ejemplo el nombre de la secuencia a usar es secuencia_idProfesor
.
Acabamos de ver cómo usar las anotaciones del estándar de JPA, pero JPA no dispone de tantos métodos de generación de claves primarias como Hibernate.
Si queremos hacer uso de todos los métodos de generación de claves primarias de que dispone Hibernate mediante el uso de anotaciones, deberemos usar la anotación propietaria de Hibernate llamada @org.hibernate.annotations.GenericGenerator
.
En este caso el código fuente de la clase Persona se debe modificar de la siguiente forma:
@Id @Column(name="Id") @GeneratedValue( generator = "generador_propietario_hibernate_increment" ) @org.hibernate.annotations.GenericGenerator( name = "generador_propietario_hibernate_increment", strategy = "increment" ) private int id;
Como podemos ver la línea 7 se ha modificado y en la línea 8 se ha añadido la anotación @org.hibernate.annotations.GenericGenerator
.
class
del tag <generator>
tal y como acabamos de ver en el fichero Profesor.hbm.xml
.
La anotación @GeneratedValue
ahora debe modificarse para usar el atributo generator
en vez de strategy
. El valor del atributo generator
debe ser el nombre del generador que hemos creado , es decir, debe tener el mismo valor que el atributo name
de la anotación @org.hibernate.annotations.GenericGenerator
.
Veamos ahora cómo se puede generar la clave primaria de forma que sea el mismo valor que la clave primaria de otro objeto. Como en el caso de usar el fichero .hbm.xml
vamos a usar como ejemplo la relación uno a uno bidireccional entre Profesor
y Direccion
. La forma de uisar la anotaciones es muy similar a la anterior de las anotaciones propietarias de Hibernate.
La forma de anotar la clase Direccion
es la siguiente:
@Id @Column(name="Id") @GeneratedValue(generator="generador_clave_ajena") @org.hibernate.annotations.GenericGenerator( name="generador_clave_ajena", strategy="foreign", parameters=@Parameter(name="property", value="profesor") ) private int id;
Vemos cómo, en esta caso, la estrategia de generación de la clave primaria es “foreign
” (Línea 6) y en la siguiente línea se indica que se debe obtener la clave primaria del objeto profesor
.
Ahora que ya tenemos preparadas la clase Java para que pueda persistirse veamos el código necesario para persistirla.
Profesor profesor=new Profesor("Eduardo", "Grau", "Aroca"); Session session=sessionFactory.openSession(); session.beginTransaction(); session.save(profesor); session.getTransaction().commit(); session.close();
Como podemos ver en la línea 1, al permitir que la clave primaria la genere Hibernate nos ahorramos tener que incluirla en el constructor. Es decir que como en otros casos , el código Java sigue estando muy simplificado gracias a Hibernate.