Herramientas de usuario

Herramientas del sitio


unidades:04_claves_primarias_y_tipos_datos:02_claves_primarias

Diferencias

Muestra las diferencias entre dos versiones de la página.


unidades:04_claves_primarias_y_tipos_datos:02_claves_primarias [2023/04/07 21:26] (actual) – creado - editor externo 127.0.0.1
Línea 1: Línea 1:
 +====== 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
 +
 +<code java 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;
 +    }
 +}
 +</code>
 +
 +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á.
 +
 +<note important>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.</note>
 +
 +En el siguiente diagrama UML muestra la clase ''Profesor''.
 +
 +<uml>
 +class Profesor 
 +Profesor : int id
 +Profesor : String nombre
 +Profesor : String ape1
 +Profesor : String ape2
 +</uml>
 +
 +===== Tablas =====
 +La tablas de base de datos quedarían de la siguiente forma:
 +
 +<uml>
 +class Profesor <<Table>>
 +Profesor : INTEGER id
 +Profesor : VARCHAR nombre
 +Profesor : VARCHAR ape1
 +Profesor : VARCHAR ape2
 +</uml>
 +
 +
 +===== 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:
 +
 +<code xml 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>
 +</code>
 +
 +
 +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. |
 +
 +
 +
 +<note>
 +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.
 +<code xml 1>
 +<id column="Id" name="id" type="integer">  
 +    <generator class="sequence" >
 +        <param name="sequence">secuencia_idProfesor</param>
 +    </generator>
 +</id>
 +</code>
 +
 +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''.
 +</note>
 +
 +<note>
 +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 [[unidades:03_relaciones:02_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:
 +<code xml 1>
 +<id column="Id" name="id" type="integer">  
 +    <generator class="foreign" >
 +        <param name="property">profesor</param>
 +    </generator>
 +</id>
 +</code>
 +
 +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''.
 +</note>
 +
 +<note tip>
 +¿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''.
 +</note>
 +===== 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:
 +
 +<code java 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;
 +    }
 +}
 +</code>
 +
 +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  |
 +
 +<note>
 +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.
 +<code java 5>
 +@Id
 +@Column(name="Id")
 +@GeneratedValue(strategy=GenerationType.SEQUENCE,generator="secuencia_idProfesor")
 +private int id;
 +</code>
 +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''.
 +</note>
 +==== 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:
 +
 +<code java 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;
 +</code>
 +
 +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:
 +<code java 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;
 +</code>
 +
 +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.
 +
 +<code java 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();
 +</code>
 +
 +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.