Herramientas de usuario

Herramientas del sitio


unidades:04_claves_primarias_y_tipos_datos:02_claves_primarias

Claves primarias

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.

Claves primarias y bases de datos

Propiedades de las claves primarias

¿Qué propiedades debe tener una buena clave primaria?

  • Debe ser única
  • No puede ser null
  • Nunca debe cambiar
  • Debe ser una única columna
  • Debe ser rápida de generar

Debe ser única

Esta propiedad es obvia para todos , así que no me extenderé más.

No puede ser null

Esta propiedad también está clara para cualquiera que sepa de bases de datos

Nunca debe cambiar

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.

Debe ser una única columna

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.

Debe ser rápida de generar

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.

La adecuada clave primaria

¿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?

  • Puede cambiar: Por normal general una clave primaria natural puede llegar a cambiar por requerimientos del propio negocio. Aunque pensemos que el NIF de una persona no cambia sí que lo hace, ya que los niños pueden sacarse un NIF en hacienda sin tener DNI y posteriormente cuando tienen DNI se cambia el NIF inicial por el del DNI.
  • Puede ser null: Quizás no tenga valor para un caso concreto del negocio. Por ejemplo, si nuestra app necesita un DNI para funcionar, los recién nacidos no tienen un DNI por lo que la pp fallaría en ciertos casos si la usáramos como clave primaria.
  • Puede ser compuesta: La clave primaria natural, por su propia naturaleza, puede estar compuesta de varias columnas y como ya hemos comentado no es muy recomendable esa circunstancia.

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.

Clases Java

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:

  • Profesor
1
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 la clases Java Profesor y Nombre no se han incluido los métodos get/set de cada propiedad para facilitar la lectura pero deben estar en la clase Java.

En el siguiente diagrama UML muestra la clase Profesor.

Profesorint idString nombreString ape1String ape2

Tablas

La tablas de base de datos quedarían de la siguiente forma:

«Table»ProfesorINTEGER idVARCHAR nombreVARCHAR ape1VARCHAR ape2

Fichero de mapeo ''.hbm.xml''

Al persistir la clase será necesario un único fichero de persistencia:

  • Profesor.hbm.xml

Profesor.hbm.xml

El fichero Profesor.hbm.xml quedará de la siguiente forma:

1| Fichero Profesor.hbm.xml
<?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.

Tag generator

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.
Al usar el método 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.
1
<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.

Al usar el método foreign es necesario indicar la propiedad del otro objeto del que se obtendrá su clave primaria.
Suponiendo que hay una relación uno a uno entre 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:
1
<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.

¿Cuál es el método mas adecuado para generar la clave primaria? Como siempre en informática dependerá de nuestro proyecto. Pero unas sugerencias que pueden ayudar son las siguientes:
  • Si debemos fusionar las bases de datos en un futuro, lo mejor es usar guid o uuid.hex.
  • El peor de los casos es usar increment ya que la consulta SELECT MAX() generará muchos bloqueos en entornos de multiples usuarios.
  • El uso de identity o sequence estará condicionado a la base de datos que usemos ya que lo normal es que tengan secuencias o columnas autoincrementales.
  • No recomiendo el uso de 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.

Anotaciones

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:

1| Clase Profesor anotada
@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.

  • @GeneratedValue: Esta anotación indica que Hibernate deberá generar el valor de la clave primaria.
    • strategy: Este atributo nos indica el método en el que Hibernate debe generar la clave primaria.En nuestro ejemplo el valor es 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
Al usar el método 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.
5
@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.

Anotaciones propietarias

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:

5
@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.

  • @org.hibernate.annotations.GenericGenerator: Esta anotación permite crear un nuevo método de generación de claves primarias para ser utilizado desde JPA.
    • name: Es el nombre del nuevo generador de claves primarias.Puede tener cualquier valor.
    • strategy: Es el nombre de un método de generación de claves primarias.Sus valores son los mismos que el del atributo 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.

foreign

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:

1
@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.

Código Java

Ahora que ya tenemos preparadas la clase Java para que pueda persistirse veamos el código necesario para persistirla.

1 | Persistiendo la clase Profesor
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.

unidades/04_claves_primarias_y_tipos_datos/02_claves_primarias.txt · Última modificación: 2023/04/07 21:26 por 127.0.0.1