Skip to content

4. : resumen

Nos proponemos presentar JPA (Persistencia Java API) con algunos ejemplos. JPA se desarrolla en el curso:

  • Persistencia de Java 5 en la práctica: [http://tahe.developpez.com/java/jpa] —proporciona las herramientas para construir la capa de acceso a datos con JPA

4.1. El lugar de JPA en una arquitectura en capas

Se invita al lector a volver a leer el inicio de este documento (párrafo 2), donde se explica el papel de la capa JPA en una arquitectura en capas. La capa JPA se integra en las capas de acceso a datos:

La capa [DAO] interactúa con la especificación JPA. Independientemente del producto que la implemente, la interfaz de la capa JPA que se presenta a la capa [DAO] sigue siendo la misma. A continuación presentamos algunos ejemplos extraídos de [ref1] que nos permitirán construir nuestra propia capa JPA.

4.2. JPA - ejemplos

4.2.1. Ejemplo 1: Representación de objeto de una sola tabla

4.2.1.1. La tabla [personne]

Consideremos una base de datos que contiene una sola tabla, [personne], cuya función es almacenar cierta información sobre personas:

 
ID
clave primaria de la tabla
VERSION
versión de la fila en la tabla. Cada vez que se modifica la información de la persona, se incrementa su número de versión.
NOM
nombre de la persona
PRENOM
su nombre
DATENAISSANCE
su fecha de nacimiento
MARIE
número entero 0 (soltero) o 1 (casado)
NBENFANTS
número de hijos de la persona

4.2.1.2. La entidad [Personne]

Nos situamos en el siguiente entorno de ejecución:

La capa JPA [5] debe servir de puente entre el mundo relacional de la base de datos [7] y el mundo de objetos [4] manipulado por los programas Java [3]. Este puente se establece mediante configuración y hay dos formas de hacerlo:

  1. con archivos XML. Esta era prácticamente la única forma de hacerlo hasta la llegada de JDK 1.5
  2. con anotaciones de Java a partir de la versión 1.5 de JDK

En este documento, utilizaremos exclusivamente el segundo método.

El objeto [Personne] que representa la tabla [personne] presentada anteriormente podría ser el siguiente:


...

@SuppressWarnings("unused")
@Entity
@Table(name="Personne")
public class Personne implements Serializable{

    @Id
    @Column(name = "ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(name = "VERSION", nullable = false)
    @Version
    private int version;

    @Column(name = "NOM", length = 30, nullable = false, unique = true)
    private String nom;

    @Column(name = "PRENOM", length = 30, nullable = false)
    private String prenom;

    @Column(name = "DATENAISSANCE", nullable = false)
    @Temporal(TemporalType.DATE)
    private Date datenaissance;

    @Column(name = "MARIE", nullable = false)
    private boolean marie;

    @Column(name = "NBENFANTS", nullable = false)
    private int nbenfants;

    // fabricantes
    public Personne() {
    }

    public Personne(String nom, String prenom, Date datenaissance, boolean marie,
            int nbenfants) {
        setNom(nom);
        setPrenom(prenom);
        setDatenaissance(datenaissance);
        setMarie(marie);
        setNbenfants(nbenfants);
    }

    // toString
    public String toString() {
...
    }

    // getter y setter
...
}

La configuración se realiza mediante anotaciones Java @Annotation. Las anotaciones de Java son interpretadas ya sea por el compilador o por herramientas especializadas en el momento de la ejecución. A excepción de la anotación de la línea 3, destinada al compilador, todas las demás anotaciones están destinadas a la implementación JPA utilizada, ya sea Hibernate o Toplink. Por lo tanto, serán interpretadas en el momento de la ejecución. Si no se cuenta con las herramientas capaces de interpretarlas, estas anotaciones se ignoran. De este modo, la clase [Personne] anterior podría utilizarse en un contexto ajeno a JPA.

Hay que distinguir dos casos de uso de las anotaciones JPA en una clase C asociada a una tabla T:

  1. la tabla T ya existe: en ese caso, las anotaciones JPA deben reproducir lo que ya existe (nombre y definición de las columnas, restricciones de integridad, claves externas, claves primarias, etc.)
  2. la tabla T no existe y se creará según las anotaciones que se encuentren en la clase C.

El caso 2 es el más fácil de manejar. Mediante las anotaciones JPA, indicamos la estructura de la tabla T que deseamos. El caso 1 suele ser más complejo. Es posible que la tabla T se haya creado, hace mucho tiempo, fuera de cualquier contexto JPA. Por lo tanto, su estructura podría no estar bien adaptada al puente relacional/objeto de JPA. Para simplificar, nos centraremos en el caso 2, en el que la tabla T asociada a la clase C se creará según las anotaciones JPA de la clase C.

Comentemos las anotaciones JPA de la clase [Personne]:

  • línea 4: la anotación @Entity es la primera anotación indispensable. Se coloca antes de la línea que declara la clase e indica que la clase en cuestión debe ser administrada por la capa de persistencia JPA. En ausencia de esta anotación, todas las demás anotaciones JPA serían ignoradas.
  • línea 5: la anotación @Table designa la tabla de la base de datos que representa la clase. Su argumento principal es name, que indica el nombre de la tabla. Si no se incluye este argumento, la tabla llevará el nombre de la clase, en este caso [Personne]. En nuestro ejemplo, la anotación @Table es, por lo tanto, innecesaria.
  • línea 8: la anotación @Id sirve para designar el campo de la clase que representa la clave primaria de la tabla. Esta anotación es obligatoria. Aquí indica que el campo id de la línea 11 representa la clave primaria de la tabla.
  • línea 9: la anotación @Column sirve para establecer el vínculo entre un campo de la clase y la columna de la tabla que representa dicho campo. El atributo name indica el nombre de la columna en la tabla. Si no se incluye este atributo, la columna lleva el mismo nombre que el campo. En nuestro ejemplo, el argumento name no era, por lo tanto, obligatorio. El argumento nullable=false indica que la columna asociada al campo no puede tener el valor NULL y que, por lo tanto, el campo debe tener necesariamente un valor.
  • línea 10: la anotación @GeneratedValue indica cómo se genera la clave primaria cuando es generada automáticamente por el SGBD. Este será el caso en todos nuestros ejemplos. Esto no es obligatorio. Así, nuestra persona podría tener un número de estudiante que sirviera como clave primaria y que no fuera generado por el SGBD, sino establecido por la aplicación. En ese caso, la anotación @GeneratedValue no estaría presente. El argumento strategy indica cómo se genera la clave primaria cuando la genera el SGBD. No todos los SGBD utilizan la misma técnica para generar los valores de la clave primaria. Por ejemplo:
Firebird
utiliza un generador de valores que se invoca antes de cada inserción
SQL server
el campo de clave primaria se define con el tipo Identity. El resultado es similar al generador de valores de Firebird, salvo que el valor de la clave solo se conoce después de insertar la fila.
Oracle
utiliza un objeto llamado SEQUENCE que, una vez más, desempeña la función de un generador de valores

La capa JPA debe generar órdenes SQL diferentes según los SGBD para crear el generador de valores. Mediante la configuración, se le indica el tipo de SGBD que debe manejar. De esta manera, puede determinar cuál es la estrategia habitual para generar valores de clave primaria de ese SGBD. El argumento strategy = GenerationType.AUTO le indica a la capa JPA que debe utilizar esta estrategia habitual. Esta técnica ha funcionado en todos los ejemplos de este documento para los siete SGBD utilizados.

  • línea 14: la anotación @Version designa el campo que se utiliza para gestionar los accesos concurrentes a una misma fila de la tabla.

Para comprender este problema de acceso concurrente a una misma fila de la tabla [personne], supongamos que una aplicación web permite actualizar los datos de una persona y analicemos el siguiente caso:

En el momento T1, un usuario U1 ingresa para modificar un registro de persona P. En ese momento, el número de hijos es 0. Cambia este número a 1, pero antes de que valide su modificación, un usuario U2 ingresa para modificar a la misma persona P. Dado que U1 aún no ha validado su modificación, U2 ve en su pantalla que el número de hijos es 0. U2 cambia el nombre de la persona P a mayúsculas. Luego, U1 y U2 validan sus modificaciones en ese orden. La modificación de U2 será la que prevalezca: en la base de datos, el nombre aparecerá en mayúsculas y el número de hijos se mantendrá en cero, aunque U1 crea haberlo cambiado a 1.

El concepto de versión de persona nos ayuda a resolver este problema. Tomemos el mismo caso de uso:

En el momento T1, un usuario U1 ingresa para modificar una persona P. En ese momento, el número de hijos es 0 y la versión es V1. Cambia el número de hijos a 1, pero antes de que valide su modificación, un usuario U2 ingresa para modificar a la misma persona P. Dado que U1 aún no ha validado su modificación, U2 ve que el número de hijos es 0 y que la versión es V1. U2 cambia el nombre de la persona P a mayúsculas. Luego, U1 y U2 validan sus modificaciones en ese orden. Antes de validar una modificación, se verifica que quien modifica a la persona P tenga la misma versión que la persona P actualmente registrada. Este será el caso del usuario U1. Por lo tanto, se acepta su modificación y se cambia la versión de la persona modificada de V1 a V2 para indicar que la persona ha sufrido un cambio. Al validar la modificación de U2, nos daremos cuenta de que U2 tiene una versión V1 de la persona P, mientras que actualmente la versión de esta es V2. Entonces podremos informarle al usuario U2 que alguien se le adelantó y que debe comenzar de nuevo con la nueva versión de la persona P. Él lo hará, recuperará una persona P de la versión V2 que ahora tiene un hijo, cambiará el nombre a mayúsculas y validará. Su modificación se aceptará si la persona P registrada sigue teniendo la versión V2. Al final, se tomarán en cuenta las modificaciones realizadas por U1 y U2, mientras que en el caso de uso sin versión, una de las modificaciones se perdía.

La capa [DAO] de la aplicación cliente puede gestionar por sí misma la versión de la clase [Personne]. Cada vez que se modifique un objeto P, la versión de ese objeto se incrementará en 1 en la tabla. La anotación @Version permite transferir esta gestión a la capa JPA. El campo en cuestión no tiene por qué llamarse version como en el ejemplo. Puede tener cualquier nombre.

Los campos correspondientes a las anotaciones @Id y @Version están presentes debido a la persistencia. No serían necesarios si la clase [Personne] no tuviera que ser persistente. Por lo tanto, se observa que un objeto no tiene la misma representación dependiendo de si necesita o no ser persistente.

  • línea 17: nuevamente la anotación @Column para proporcionar información sobre la columna de la tabla [personne] asociada al campo nom de la clase Personne. Aquí encontramos dos nuevos argumentos:
    • unique=true indica que el nombre de una persona debe ser único. Esto se traducirá en la base de datos mediante la adición de una restricción de unicidad en la columna NOM de la tabla [personne].
    • length=30 establece en 30 el número de caracteres de la columna NOM. Esto significa que el tipo de esta columna será VARCHAR(30).
  • línea 24: la anotación @Temporal sirve para indicar qué tipo SQL se le debe asignar a una columna o campo de tipo fecha/hora. El tipo TemporalType.DATE designa una fecha sola sin hora asociada. Los otros tipos posibles son TemporalType.TIME para codificar una hora y TemporalType.TIMESTAMP para codificar una fecha con hora.

Ahora comentemos el resto del código de la clase [Personne]:

  • línea 6: la clase implementa la interfaz Serializable. La sérialisation de un objeto consiste en transformarlo en una secuencia de bits. La désérialisation es la operación inversa. La serialización y deserialización se utilizan, en particular, en aplicaciones cliente-servidor donde los objetos se intercambian a través de la red. Las aplicaciones cliente o servidor no tienen conocimiento de esta operación, la cual se realiza de manera transparente mediante los JVM. Sin embargo, para que esto sea posible, es necesario que las clases de los objetos intercambiados estén «etiquetadas» con la palabra clave Serializable.
  • Línea 37: un constructor de la clase. Cabe señalar que los campos id y version no forman parte de los parámetros. De hecho, estos dos campos son administrados por la capa JPA y no por la aplicación.
  • Líneas 51 y siguientes: los métodos get y set de cada uno de los campos de la clase. Cabe señalar que las anotaciones JPA pueden colocarse en los métodos get de los campos en lugar de en los campos mismos. La ubicación de las anotaciones indica el modo que debe utilizar JPA para acceder a los campos:
    • si las anotaciones se colocan a nivel de campo, JPA accederá directamente a los campos para leerlos o escribirlos
    • si las anotaciones se colocan a nivel de get, JPA accederá a los campos a través de los métodos get / set para leerlos o escribirlos

Es la posición de la anotación @Id la que determina la posición de las anotaciones JPA en una clase. Si se coloca a nivel de campo, indica un acceso directo a los campos; y si se coloca a nivel de get, indica un acceso a los campos mediante los métodos get y set. Las demás anotaciones deben colocarse de la misma manera que la anotación @Id.

4.2.2. Configuración de la capa JPA

Las pruebas de la capa JPA se pueden realizar con la siguiente arquitectura:

  • en [7]: la base de datos que se generará a partir de las anotaciones de la entidad [Personne], así como de configuraciones adicionales realizadas en un archivo llamado [persistence.xml]
  • en [5, 6]: una capa JPA implementada por Hibernate
  • en [4]: la entidad [Personne]
  • en [3]: un programa de prueba de tipo consola

La configuración de la capa JPA se realiza mediante el archivo [META-INF/persistence.xml]:

Al ejecutarse, se busca el archivo [META-INF/persistence.xml] en el archivo Classpath de la aplicación.

Veamos la configuración de la capa JPA definida en el archivo [persistence.xml] de nuestro proyecto:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
        <!--  proveedor -->
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <properties>
            <!-- Clases persistentes -->
            <property name="hibernate.archive.autodetection" value="class, hbm" />
            <!-- registros SQL
                <property name="hibernate.show_sql" value="true"/>
                <property name="hibernate.format_sql" value="true"/>
                <property name="use_sql_comments" value="true"/>
            -->
            <!-- conexión JDBC -->
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
            <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa" />
            <property name="hibernate.connection.username" value="jpa" />
            <property name="hibernate.connection.password" value="jpa" />
            <!-- creación automática del esquema -->
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <!-- Dialecto -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
            <!-- propiedades DataSource c3p0 -->
            <property name="hibernate.c3p0.min_size" value="5" />
            <property name="hibernate.c3p0.max_size" value="20" />
            <property name="hibernate.c3p0.timeout" value="300" />
            <property name="hibernate.c3p0.max_statements" value="50" />
            <property name="hibernate.c3p0.idle_test_period" value="3000" />
        </properties>
    </persistence-unit>
</persistence>

Para entender esta configuración, debemos repasar la arquitectura de acceso a los datos de nuestra aplicación:

  • El archivo [persistence.xml] configurará las capas [4, 5, 6]
  • [4]: implementación de Hibernate de JPA
  • [5]: Hibernate accede a la base de datos a través de un grupo de conexiones. Un grupo de conexiones es una reserva de conexiones abiertas con el SGBD. Un SGBD es utilizado por múltiples usuarios, aunque, por razones de rendimiento, no puede superar un número límite N de conexiones abiertas simultáneamente. Un código bien escrito abre una conexión con el SGBD por el menor tiempo posible: envía órdenes al SQL y cierra la conexión. Lo hará de manera repetida, cada vez que necesite trabajar con la base de datos. El costo de abrir y cerrar una conexión no es insignificante, y ahí es donde entra en juego el grupo de conexiones. Al iniciar la aplicación, este grupo abrirá N1 conexiones con el SGBD. Es a este al que la aplicación le solicitará una conexión abierta cuando la necesite. Dicha conexión se devolverá al grupo tan pronto como la aplicación ya no la necesite, preferiblemente lo más rápido posible. La conexión no se cierra y permanece disponible para el siguiente usuario. Por lo tanto, un grupo de conexiones es un sistema para compartir conexiones abiertas.
  • [6]: el controlador JDBC del SGBD utilizado

Ahora veamos cómo el archivo [persistence.xml] configura las capas [4, 5, 6] mencionadas anteriormente:

  • línea 2: la etiqueta raíz del archivo XML es <persistence>.
  • línea 3: <persistence-unit> sirve para definir una unidad de persistencia. Puede haber varias unidades de persistencia. Cada una de ellas tiene un nombre (atributo name) y un tipo de transacción (atributo transaction-type). La aplicación tendrá acceso a la unidad de persistencia a través de su nombre, en este caso jpa. El tipo de transacción RESOURCE_LOCAL indica que la aplicación gestiona por sí misma las transacciones con el SGBD. Este será el caso aquí. Cuando la aplicación se ejecuta en un contenedor EJB3, puede utilizar el servicio de transacciones de este. En este caso, se establecerá transaction-type=JTA (transacción Java API). JTA es el valor por defecto cuando no se incluye el atributo transaction-type.
  • línea 5: la etiqueta <provider> sirve para definir una clase que implementa la interfaz [javax.persistence.spi.PersistenceProvider], interfaz que permite a la aplicación inicializar la capa de persistencia. Dado que se utiliza una implementación de JPA / Hibernate, la clase utilizada aquí es una clase de Hibernate.
  • línea 6: la etiqueta <properties> introduce propiedades específicas del provider concreto que se haya elegido. Así, dependiendo de si se ha elegido Hibernate, Toplink, Kodo, etc., se tendrán propiedades diferentes. Las que siguen son específicas de Hibernate.
  • línea 8: le pide a Hibernate que explore el classpath del proyecto para encontrar las clases que tengan la anotación @Entity y así poder administrarlas. Las clases @Entity también pueden declararse mediante las etiquetas <class>nom_de_la_classe</class>, directamente debajo de la etiqueta <persistence-unit>. Esto es lo que haremos con el provider JPA / Toplink.
  • Las líneas 10 a 12, que aquí aparecen como comentarios, configuran los registros de consola de Hibernate:
    • línea 10: para mostrar o no los comandos SQL emitidos por Hibernate en el SGBD. Esto es muy útil durante la fase de aprendizaje. Debido al puente relacional/objeto, la aplicación trabaja con objetos persistentes a los que aplica operaciones del tipo [persist, merge, remove]. Es muy interesante saber cuáles son los comandos SQL que realmente se emiten en estas operaciones. Al estudiarlas, poco a poco se va adivinando cuáles son las órdenes SQL que Hibernate generará al realizar tal operación sobre los objetos persistentes, y el puente relacional/objeto comienza a tomar forma en la mente.
    • línea 11: las órdenes SQL que se muestran en la consola se pueden formatear de manera atractiva para facilitar su lectura
    • línea 12: además, los comandos SQL que se muestren llevarán comentarios
  • Las líneas 15-19 definen la capa JDBC (capa [6] en la arquitectura):
    • línea 15: la clase del controlador JDBC del SGBD, en este caso MySQL5
    • línea 16: la URL de la base de datos utilizada
    • líneas 17 y 18: el usuario de la conexión y su contraseña
  • línea 22: Hibernate necesita conocer el SGBD con el que está trabajando. De hecho, todos los SGBD tienen extensiones SQL propias, una forma específica de gestionar la generación automática de los valores de una clave primaria, ... lo que hace que Hibernate necesite conocer el SGBD con el que está trabajando para enviarle las órdenes SQL que este entenderá. [MySQL5InnoDBDialect] designa al SGBD MySQL5 con tablas de tipo InnoDB que admiten transacciones.
  • Las líneas 24 a 28 configuran el grupo de conexiones c3p0 (capa [5] en la arquitectura):
    • líneas 24 y 25: el número mínimo (por defecto 3) y máximo de conexiones (por defecto 15) en el grupo. El número inicial de conexiones por defecto es 3.
    • línea 26: tiempo máximo, en milisegundos, de espera de una solicitud de conexión por parte del cliente. Pasado este tiempo, c3p0 le devolverá una excepción.
    • línea 27: para acceder a la orden BD, Hibernate utiliza órdenes SQL preparadas (PreparedStatement) que c3p0 puede almacenar en caché. Esto significa que si la aplicación solicita por segunda vez una orden SQL preparada que ya se encuentra en la caché, no será necesario prepararla (la preparación de una orden SQL tiene un costo) y se utilizará la que está en la caché. Aquí se indica el número máximo de órdenes SQL preparadas que puede contener la caché, considerando todas las conexiones (una orden SQL preparada pertenece a una conexión).
    • línea 28: frecuencia de verificación, en milisegundos, de la validez de las conexiones. Una conexión del grupo puede volverse inválida por diversas razones (el controlador JDBC invalida la conexión porque dura demasiado, el controlador JDBC presenta «errores», etc.).
  • línea 20: aquí se solicita que, al inicializar la unidad de persistencia, se genere la base de datos de los objetos @Entity. Hibernate cuenta ahora con todas las herramientas para emitir las órdenes SQL de generación de las tablas de la base de datos:
    • la configuración de los objetos @Entity le permite saber qué tablas debe generar
    • las líneas 15-18 y 24-28 le permiten establecer una conexión con el SGBD
    • la línea 22 le permite saber qué dialecto SQL debe usar para generar las tablas

De este modo, el archivo [persistence.xml] utilizado aquí recrea una base de datos nueva cada vez que se ejecuta la aplicación. Las tablas se recrean (create table) después de haber sido eliminadas (drop table) si ya existían. Cabe señalar que, obviamente, esto no debe hacerse en una base de datos en producción...

4.2.3. Ejemplo 2: relación uno a varios

4.2.3.1. El esquema de la base de datos « »

 
1
2

    alter table jpa06_article 
        drop 
        foreign key FKFFBDD9D8ECCE8750;

    drop table if exists jpa06_article;

    drop table if exists jpa06_categorie;

    create table jpa06_article (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30),
        categorie_id bigint not null,
        primary key (id)
    ) ENGINE=InnoDB;

    create table jpa06_categorie (
        id bigint not null auto_increment,
        version integer not null,
        nom varchar(30),
        primary key (id)
    ) ENGINE=InnoDB;

    alter table jpa06_article 
        add index FKFFBDD9D8ECCE8750 (categorie_id), 
        add constraint FKFFBDD9D8ECCE8750 
        foreign key (categorie_id) 
references jpa06_categorie (id);
  • en [1], la base de datos, y en [2], su DDL (MySQL5)

Un artículo A(id, versión, nombre) pertenece exactamente a una categoría C(id, versión, nombre). Una categoría C puede contener 0, 1 o varios artículos. Existe una relación uno-a-varios (Categoría -> Artículo) y la relación inversa varios-a-uno (Artículo -> Categoría). Esta relación se materializa mediante la clave externa que posee la tabla [article] sobre la tabla [categorie] (líneas 24-28 de la DDL).

4.2.3.2. Los objetos @Entity que representan la base de datos

Un artículo se representa mediante la siguiente @Entity [Article]:


package entites;

...
@Entity
@Table(name="jpa05_hb_article")
public class Article implements Serializable {

    // campos
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @SuppressWarnings("unused")
    @Version
    private int version;

    @Column(length = 30)
    private String nom;

    // relación principal Artículo (muchos) -> Categoría (uno)
    // implementada mediante una clave externa (categorie_id) en Artículo
    // 1 Artículo tiene necesariamente 1 Categoría (nullable=false)
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name = "categorie_id", nullable = false)
    private Categorie categorie;

    // constructores
    public Article() {
    }

    // getters y setters
    ...
    // toString
    public String toString() {
        return String.format("Article[%d,%d,%s,%d]", id, version, nom, categorie.getId());
    }

}
  • líneas 9-11: clave primaria de la @Entity
  • líneas 13-15: su número de versión
  • líneas 17-18: nombre del artículo
  • líneas 20-25: relación de varios a uno que vincula la @Entity Article con la @Entity Categorie:
    • línea 23: la anotación ManyToOne. El «Many» se refiere a la @Entity Article en la que nos encontramos y el «One» a la @Entity Categorie (línea 25). Una categoría (One) puede tener varios artículos (Many).
    • línea 24: la anotación ManyToOne define la columna de clave externa en la tabla [article]. Se llamará (name) categorie_id y cada fila deberá tener un valor en esta columna (nullable=false).
    • línea 25: la categoría a la que pertenece el artículo. Cuando un artículo se coloque en el contexto de persistencia, se solicita que su categoría no se incluya allí de inmediato (fetch=FetchType.LAZY, línea 23). No sabemos si esta solicitud tiene sentido. Ya lo veremos.

Una categoría está representada por la siguiente @Entity [Categorie]:


package entites;
...
@Entity
@Table(name="jpa05_hb_categorie")
public class Categorie implements Serializable {

    // campos
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @SuppressWarnings("unused")
    @Version
    private int version;

    @Column(length = 30)
    private String nom;

    // relación inversa Categoría (uno) -> Artículo (muchos) de la relación Artículo (muchos) -> Categoría (uno)
    // inserción en cascada de Categoría -> inserción de Artículos
    // cascada de actualización de Categoría -> actualización de Artículos
    // eliminación en cascada de Categoría -> eliminación de Artículos
    @OneToMany(mappedBy = "categorie", cascade = { CascadeType.ALL })
    private Set<Article> articles = new HashSet<Article>();

    // constructores
    public Categorie() {
    }

    // getters y setters
...
    // toString
    public String toString() {
        return String.format("Categorie[%d,%d,%s]", id, version, nom);
    }

    // asociación bidireccional Categoría <--> Artículo
    public void addArticle(Article article) {
        // el artículo se agrega a la colección de artículos de la categoría
        articles.add(article);
        // el artículo cambia de categoría
        article.setCategorie(this);
    }
}
  • líneas 8-11: la clave primaria de la @Entity
  • líneas 12-14: su versión
  • líneas 16-17: el nombre de la categoría
  • líneas 19-24: el conjunto (set) de artículos de la categoría
    • línea 23: la anotación @OneToMany indica una relación uno a varios. El «One» se refiere a la @Entity [Categorie] en la que nos encontramos, y el «Many» al tipo [Article] de la línea 24: una (One) categoría tiene varios (Many) artículos.
    • línea 23: la anotación es la inversa (mappedBy) de la anotación ManyToOne colocada en el campo categorie de la @Entity Article: mappedBy=categoría. La relación ManyToOne, definida en el campo categorie de la @Entity Article, es la relación principal. Es indispensable. Esta relación representa la relación de clave externa que vincula la @Entity Article con la @Entity Categorie. La relación OneToMany, definida sobre el campo articles de la @Entity Categorie, es la relación inversa. No es indispensable. Es una facilidad para obtener los artículos de una categoría. Sin esta facilidad, dichos artículos se obtendrían mediante una consulta JPQL.
    • línea 23: cascadeType.ALL solicita que las operaciones (persist, merge, remove) realizadas en una @Entity Categorie se apliquen en cascada a sus artículos.
    • línea 24: los artículos de una categoría se colocarán en un objeto de tipo Set<Article>. El tipo Set no admite duplicados. Por lo tanto, no se puede incluir dos veces el mismo artículo en el objeto Set<Article>. ¿Qué significa «el mismo artículo»? Para indicar que el artículo a es el mismo que el artículo b, Java utiliza la expresión a.equals(b). En la clase Object, clase padre de todas las clases, a.equals(b) es verdadera si a==b, c.a.d. si los objetos a y b tienen la misma ubicación en memoria. Podríamos querer decir que los artículos a y b son los mismos si tienen el mismo nombre. En este caso, el desarrollador debe redefinir dos métodos en la clase [Article]:
  • equals: debe devolver verdadero si ambos artículos tienen el mismo nombre
  • hashCode: debe devolver un valor entero idéntico para dos objetos [Article] que el método equals considere iguales. En este caso, el valor se generará a partir del nombre del artículo. El valor devuelto por hashCode puede ser cualquier número entero. Se utiliza en diferentes contenedores de objetos, en particular en los diccionarios (Hashtable).

La relación OneToMany puede utilizar otros tipos además de Set para almacenar el Many, como objetos de tipo List, por ejemplo. No abordaremos estos casos en este documento. El lector los encontrará en [ref1].

  • línea 38: el método [addArticle] nos permite agregar un artículo a una categoría. El método se encarga de actualizar ambos extremos de la relación OneToMany que vincula a [Categorie] con [Article].

4.3. El API de la capa JPA

Explicaremos el entorno de ejecución de un cliente JPA:

Sabemos que la capa JPA [2] crea un puente entre objetos [3] y relaciones [4]. Se denomina «contexto de persistencia» al conjunto de objetos gestionados por la capa JPA en el marco de este puente entre objetos y relaciones. Para acceder a los datos del contexto de persistencia, un cliente JPA [1] debe pasar por la capa JPA [2]:

  1. puede crear un objeto y solicitar a la capa JPA que lo haga persistente. El objeto pasa entonces a formar parte del contexto de persistencia.
  2. puede solicitar a la capa [JPA] una referencia a un objeto persistente existente.
  3. puede modificar un objeto persistente obtenido de la capa JPA.
  4. puede solicitar a la capa JPA que elimine un objeto del contexto de persistencia.

La capa JPA presenta al cliente una interfaz llamada [EntityManager] que, como su nombre lo indica, permite gestionar los objetos @Entity del contexto de persistencia. A continuación, presentamos los principales métodos de esta interfaz:

void persist(Object entity)
coloca entity en el contexto de persistencia
void remove(Object entity)
elimina entity del contexto de persistencia
<T> T merge(T entity)
fusiona un objeto entity del cliente no administrado por el contexto de persistencia
con el objeto entity del contexto de persistencia que tiene la misma clave primaria.
El resultado devuelto es el objeto entity del contexto de persistencia.
<T> T find(Class<T> entityClass,
Object primaryKey)
inserta en el contexto de persistencia un objeto buscado en la base de datos
mediante su clave primaria. El tipo T del objeto permite a la capa JPA
saber qué tabla consultar. El objeto persistente así creado se devuelve al cliente.
Query createQuery(String queryText)
crea un objeto Query a partir de una consulta JPQL (Java Persistence
Query Language). Una consulta JPQL es análoga a una consulta SQL si
excepto que consulta objetos en lugar de tablas.
Query createNativeQuery(String queryText)
método similar al anterior, salvo que queryText es un
orden SQL y no JPQL.
Query createNamedQuery(String name)
Método idéntico al de createQuery, salvo que el orden JPQL queryText se ha
se ha externalizado a un archivo de configuración y se le ha asignado un nombre.
Este nombre es el parámetro del método.

Un objeto EntityManager tiene un ciclo de vida que no es necesariamente el de la aplicación. Tiene un inicio y un final. Así, un cliente JPA puede trabajar sucesivamente con diferentes objetos EntityManager. El contexto de persistencia asociado a un EntityManager tiene el mismo ciclo de vida que él. Son inseparables el uno del otro. Cuando se cierra un objeto EntityManager, su contexto de persistencia se sincroniza, si es necesario, con la base de datos y luego deja de existir. Es necesario crear un nuevo EntityManager para volver a tener un contexto de persistencia.

El cliente JPA puede crear un EntityManager y, por lo tanto, un contexto de persistencia con la siguiente instrucción:


EntityManagerFactory emf = Persistence.createEntityManagerFactory("nom d'une unité de persistance");
  • javax.persistence.Persistence es una clase estática que permite obtener un generador (factory) de objetos EntityManager. Este generador está vinculado a una unidad de persistencia específica. Recordemos que el archivo de configuración [META-INF/persistence.xml] permite definir unidades de persistencia y que estas tienen un nombre:

    <persistence-unit name="elections-dao-jpa-mysql-01PU" transaction-type="RESOURCE_LOCAL">

En el ejemplo anterior, la unidad de persistencia se llama elections-dao-jpa-mysql-01PU. Con ella viene toda una configuración propia, en particular el SGBD con el que trabaja. La instrucción [Persistence.createEntityManagerFactory("elections-dao-jpa-mysql-01PU")] crea una fábrica de objetos de tipo EntityManagerFactory capaz de proporcionar objetos EntityManager destinados a gestionar contextos de persistencia vinculados a la unidad de persistencia denominada elections-dao-jpa-mysql-01PU. La obtención de un objeto EntityManager y, por lo tanto, de un contexto de persistencia, se realiza a partir del objeto EntityManagerFactory de la siguiente manera:

        EntityManager em = emf.createEntityManager();

Los siguientes métodos de la interfaz [EntityManager] permiten gestionar el ciclo de vida del contexto de persistencia:

void close()
Se cierra el contexto de persistencia. Fuerza la sincronización del contexto de persistencia con la base de datos:
  • si un objeto del contexto no está presente en la base de datos, se inserta mediante una operación (SQL, INSERT)
  • si un objeto del contexto está presente en la base de datos y ha sido modificado desde que se leyó, se realiza una operación (SQL UPDATE) para guardar la modificación
  • si un objeto del contexto ha sido marcado como «eliminado» al finalizar una operación remove sobre él, se realiza una operación SQL DELETE para eliminarlo de la base de datos.
void clear()
El contexto de persistencia se vacía de todos sus objetos, pero no se cierra.
void flush()
el contexto de persistencia se sincroniza con la base de datos de la manera descrita para close()

El cliente JPA puede forzar la sincronización del contexto de persistencia con la base de datos mediante el método [EntityManager].flush anterior. La sincronización puede ser explícita o implícita. En el primer caso, es el cliente quien debe realizar las operaciones flush cuando desee llevar a cabo sincronizaciones; de lo contrario, estas se realizan en determinados momentos que especificaremos a continuación. El modo de sincronización se gestiona mediante los siguientes métodos de la interfaz [EntityManager]:

void setFlushMode(FlushModeType
flushMode)
Hay dos valores posibles para flushmode:
FlushModeType.AUTO (por defecto): la sincronización se lleva a cabo antes
cada consulta SELECT que se realice en la base de datos.
FlushModeType.COMMIT: la sincronización solo se lleva a cabo al
el final de las transacciones en la base de datos.
FlushModeType getFlushMode()
muestra el modo actual de sincronización

Resumamos. En el modo FlushModeType.AUTO, que es el modo predeterminado, el contexto de persistencia se sincronizará con la base de datos en los siguientes momentos:

  1. antes de cada operación SELECT en la base de datos
  2. al final de una transacción en la base de datos
  3. después de una operación flush o close en el contexto de persistencia

En el modo FlushModeType.COMMIT, ocurre lo mismo, salvo por la operación 1, que no se lleva a cabo. El modo normal de interacción con la capa JPA es un modo transaccional. El cliente realiza diversas operaciones en el contexto de persistencia, dentro de una transacción. En este caso, los momentos de sincronización del contexto de persistencia con la base de datos son los casos 1 y 2 mencionados anteriormente en el modo AUTO, y únicamente el caso 2 en el modo COMMIT.

Terminemos con el API de la interfaz Query, interfaz que permite emitir órdenes JPQL en el contexto de persistencia o bien órdenes SQL directamente en la base de datos para recuperar datos de ella. La interfaz Query es la siguiente:

  • 1 - El método getResultList ejecuta un SELECT que devuelve varios objetos. Estos se obtendrán en un objeto List. Este objeto es una interfaz. Esta ofrece un objeto Iterator que permite recorrer los elementos de la lista L de la siguiente manera:

        Iterator iterator = L.iterator();
        while (iterator.hasNext()) {
             // utilizar el objeto iterator.next() que representa el elemento actual de la lista
...
}

La lista L también se puede utilizar con un for:


        for (Object o : L) {
             // utilizar el objeto o
}
  • 2 - El método getSingleResult ejecuta una orden JPQL / SQL SELECT que devuelve un único objeto.
  • 3 - El método executeUpdate ejecuta una orden SQL de actualización o eliminación y devuelve el número de filas afectadas por la operación.
  • 4 - El método setParameter(String, Object) permite asignar un valor a un parámetro con nombre de una orden JPQL configurada
  • 5 - El método setParameter(int, Object) no designa el parámetro por su nombre, sino por su posición en la orden JPQL.

4.4. Las consultas JPQL

JPQL (Java Persistence Query Language) es el lenguaje de consultas de la capa JPA. El lenguaje JPQL es similar al lenguaje SQL de las bases de datos. Mientras que SQL trabaja con tablas, JPQL trabaja con los objetos de imagen de esas tablas. Analizaremos un ejemplo dentro de la siguiente arquitectura:

La base de datos, a la que llamaremos [dbrdvmedecins2] , es una base de datos MySQL5 con cuatro tablas:

  

Recopila información que permite administrar las citas de un grupo de médicos.

4.4.1. La tabla [MEDECINS]

Contiene información sobre los médicos.

  • ID: número que identifica al médico —clave primaria de la tabla
  • VERSION: número que identifica la versión de la fila en la tabla. Este número se incrementa en 1 cada vez que se realiza una modificación en la fila.
  • NOM: el apellido del médico
  • PRENOM: su nombre
  • TITRE: su título (Srta., Sra., Sr.)

4.4.2. La tabla [CLIENTS]

Los clientes de los distintos médicos se registran en la tabla [CLIENTS]:

  • ID: número que identifica al cliente —clave primaria de la tabla
  • VERSION: número que identifica la versión de la fila en la tabla. Este número se incrementa en 1 cada vez que se realiza una modificación en la fila.
  • NOM: el nombre del cliente
  • PRENOM: su nombre
  • TITRE: su tratamiento (Srta., Sra., Sr.)

4.4.3. La tabla [CRENEAUX]

Enumera los horarios en los que son posibles los RV:

  • ID: número que identifica el horario —clave primaria de la tabla (línea 8)
  • VERSION: número que identifica la versión de la fila en la tabla. Este número se incrementa en 1 cada vez que se realiza una modificación en la fila.
  • ID_MEDECIN: número que identifica al médico al que pertenece este horario – clave externa en la columna MEDECINS (ID).
  • HDEBUT: hora de inicio de la franja horaria
  • MDEBUT: minutos de inicio del horario
  • HFIN: hora de finalización del horario
  • MFIN: minutos de fin de la franja

La segunda línea de la tabla [CRENEAUX] (véase [1] más arriba) indica, por ejemplo, que el horario n.º 2 comienza a las 8:20 y termina a las 8:40, y pertenece a la doctora n.º 1 (la Sra. Marie PELISSIER).

4.4.4. La tabla [RV]

Enumera los RV asignados a cada médico:

  • ID: número que identifica de manera única al RV – clave primaria
  • JOUR: día del RV
  • ID_CRENEAU: franja horaria del RV —clave externa en el campo [ID] de la tabla [CRENEAUX]—; determina tanto la franja horaria como el médico en cuestión.
  • ID_CLIENT: número del cliente para quien se realiza la reservación – clave externa en el campo [ID] de la tabla [CLIENTS]

Esta tabla tiene una restricción de unicidad en la e sobre los valores de las columnas unidas (JOUR, ID_CRENEAU):

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);

Si una fila de la tabla [RV] tiene el valor (JOUR1, ID_CRENEAU1) para las columnas (JOUR, ID_CRENEAU), este valor no puede aparecer en ningún otro lugar. De lo contrario, significaría que se registraron dos RV al mismo tiempo para el mismo médico. Desde el punto de vista de la programación en Java, el controlador JDBC de la base de datos inicia un SQLException cuando ocurre este caso.

La línea de id igual a 3 (véase [1] más arriba) significa que se reservó un RV para el horario n.º 20 y el cliente n.º 4 el 23/08/2006. La tabla [CRENEAUX] nos indica que el horario n.º 20 corresponde al intervalo de 16:20 a 16:40 y pertenece a la doctora n.º 1 (la Sra. Marie PELISSIER). La tabla [CLIENTS] nos indica que el cliente n.º 4 es la Srta. Brigitte BISTROU.

4.4.5. Generación de la base de datos

Para crear las tablas y llenarlas, se puede utilizar el script [dbrdvmedecins2.sql]. Con [WampServer], se puede proceder de la siguiente manera:

  • En [1], haz clic en el ícono de [WampServer] y selecciona la opción [PhpMyAdmin] [2],
  • en [3], en la ventana que se abrió, selecciona el enlace [Bases de données],
  • a [2], se crea una base de datos a la que se le ha asignado el nombre [4] y la codificación [5],
  • en [7], la base de datos ya se ha creado. Hacemos clic en su enlace,
  • en [8], se importa un archivo SQL,
  • que se selecciona en el sistema de archivos con el botón [9],
  • en [11], se selecciona el script SQL y en [12] se ejecuta,
  • en [13], se han creado las cuatro tablas de la base de datos. Seguimos uno de los enlaces,
  • en [14], el contenido de la tabla.

A partir de ahora, no volveremos a referirnos a esta base de datos. Sin embargo, invitamos al lector a seguir su evolución a lo largo de los programas, especialmente cuando algo no funcione.

4.4.6. La capa [JPA]

Volvamos a la arquitectura del ejemplo:

Ahora construiremos el proyecto Maven de la capa [JPA].

4.4.7. El proyecto de NetBeans

Es el siguiente:

  • en [1], se crea un proyecto Maven de tipo [Java Application] [2],
  • en [3], se le da un nombre al proyecto,
  • en [4], el proyecto generado.

4.4.8. Generación de la capa [JPA]

Volvamos a la arquitectura que debemos construir:

Con NetBeans, es posible generar automáticamente la capa [JPA]. Es interesante conocer estos métodos de generación automática, ya que el código generado ofrece valiosas indicaciones sobre cómo escribir entidades JPA.

4.4.9. Creación de una conexión de NetBeans a la base de datos

  • ejecutar el SGBD MySQL 5 para que el BD esté disponible,
  • crea una conexión de NetBeans a la base de datos [dbrdvmedecins2],
  • en la pestaña [Services] [1], en la rama [Databases] [2], seleccione el controlador JDBC MySQL [3],
  • luego seleccione la opción [4] «Connect Using», que permite establecer una conexión con una base de datos MySQL,
  • en [5], ingresa la información que se solicita. En [6], el nombre de la base de datos; en [7], el usuario de la base de datos y su contraseña;
  • en [8], se puede verificar la información que se ha proporcionado,
  • en [9], el mensaje que se espera recibir cuando la información es correcta,
  • en [10], se establece la conexión. Aquí se ven las cuatro tablas de la base de datos conectada.

4.4.10. Creación de una unidad de persistencia

Volvamos a la arquitectura que estamos construyendo:

Estamos construyendo la capa [JPA]. Su configuración se realiza en un archivo [persistence.xml], en el que se definen las unidades de persistencia. Cada una de ellas requiere la siguiente información:

  • las características JDBC de acceso a la base de datos (URL, usuario, contraseña),
  • las clases que servirán como representaciones de las tablas de la base de datos,
  • la implementación JPA utilizada. De hecho, JPA es una especificación implementada por diversos productos. En este caso, utilizaremos Hibernate.

NetBeans puede generar este archivo de persistencia mediante un asistente.

  • Haga clic con el botón derecho en el proyecto y elija crear una unidad de persistencia [1],
  • en [2], crear una unidad de persistencia,
  • en [3], asignar un nombre a la unidad de persistencia que se está creando,
  • en [4], seleccionar la implementación JPA de Hibernate (JPA 2.0),
  • en [5], indicar que las tablas de BD ya están creadas y que, por lo tanto, no se crearán. Se valida el asistente,
  • en [6], el nuevo proyecto,
  • en [7], se generó el archivo [persistence.xml] en la carpeta [META-INF],
  • en [8], se han agregado nuevas dependencias al proyecto Maven.

El archivo [META-INF/persistence.xml] generado es el siguiente:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
    </properties>
  </persistence-unit>
</persistence>

Incluye la información proporcionada en el asistente:

  • línea 3: el nombre de la unidad de persistencia,
  • línea 3: el tipo de transacciones con la base de datos. Aquí, RESOURCE_LOCAL indica que la aplicación administrará sus propias transacciones,
  • líneas 6-9: las propiedades JDBC de la fuente de datos.

En la pestaña [Design], se puede obtener una vista general del archivo [persistence.xml]:

Para obtener los registros de Hibernate, completamos el archivo [persistence.xml] de la siguiente manera:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>      
    </properties>
  </persistence-unit>
</persistence>
  • línea 11: se solicita ver las órdenes SQL emitidas por Hibernate,
  • línea 12: esta propiedad permite obtener una visualización formateada de las mismas.

Se han agregado dependencias al proyecto. El archivo [pom.xml] es el siguiente:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>istia.st</groupId>
  <artifactId>mv-rdvmedecins-jpql-hibernate</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-rdvmedecins-jpql-hibernate</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
    </dependency>
    <dependency>
      <groupId>org.jboss.logging</groupId>
      <artifactId>jboss-logging</artifactId>
      <version>3.1.0.GA</version>
    </dependency>
    <dependency>
      <groupId>org.jboss.spec.javax.transaction</groupId>
      <artifactId>jboss-transaction-api_1.1_spec</artifactId>
      <version>1.0.0.Final</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>4.1.2</version>
    </dependency>
    <dependency>
      <groupId>antlr</groupId>
      <artifactId>antlr</artifactId>
      <version>2.7.7</version>
    </dependency>
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6.1</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate.javax.persistence</groupId>
      <artifactId>hibernate-jpa-2.0-api</artifactId>
      <version>1.0.1.Final</version>
    </dependency>
    <dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.15.0-GA</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate.common</groupId>
      <artifactId>hibernate-commons-annotations</artifactId>
      <version>4.0.1.Final</version>
    </dependency>
  </dependencies>
</project>

Todas las dependencias agregadas se refieren a Hibernate (ORM). Agregaremos la dependencia del controlador JDBC de MySQL:


    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>        

4.4.11. Generación de entidades JPA

Las entidades JPA se pueden generar mediante un asistente de NetBeans:

  • en [1], se crean entidades JPA a partir de una base de datos,
  • en [2], se selecciona la conexión [dbrdvmedecins2] creada anteriormente,
  • en [3], se seleccionan todas las tablas de la base de datos asociada,
  • en [4], se asigna un nombre a las clases Java asociadas a las cuatro tablas,
  • así como un nombre de paquete [5],
  • en [6], JPA agrupa las filas de las tablas de BD en colecciones. Elegimos la lista como colección,
  • en [7], las clases de Java creadas por el asistente.

4.4.12. Las entidades JPA generadas

La entidad [Medecin] es la representación de la tabla [medecins]. La clase Java está repleta de anotaciones que hacen que el código resulte poco legible a primera vista. Si nos quedamos solo con lo esencial para comprender la función de la entidad, obtenemos el siguiente código:


package rdvmedecins.jpa;

...
@Entity
@Table(name = "medecins")
public class Medecin implements Serializable {
  
@Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;
  
  @Column(name = "TITRE")
  private String titre;

  @Column(name = "NOM")
  private String nom;

  @Column(name = "VERSION")
  private int version;

  @Column(name = "PRENOM")
  private String prenom;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
  private List<Creneau> creneauList;

// constructores
....

   // getters y setters
....

  @Override
  public int hashCode() {
  ...
  }

  @Override
  public boolean equals(Object object) {
  ...
  }

  @Override
  public String toString() {
    ...
  }
  
}
  • En la línea 4, la anotación @Entity convierte a la clase [Medecin] en una entidad JPA, c.a.d. una clase vinculada a una tabla de BD a través de API y JPA,
  • línea 5, el nombre de la tabla BD asociada a la entidad JPA. Cada campo de la tabla corresponde a un campo en la clase Java,
  • línea 6, la clase implementa la interfaz Serializable. Esto es necesario en aplicaciones cliente/servidor, donde las entidades se serializan entre el cliente y el servidor.
  • líneas 10-11: el campo id de la clase [Medecin] corresponde al campo [ID] (línea 10) de la tabla [medecins],
  • líneas 13-14: el campo «título» de la clase [Medecin] corresponde al campo [TITRE] (línea 13) de la tabla [medecins],
  • líneas 16-17: el campo «nombre» de la clase [Medecin] corresponde al campo [NOM] (línea 16) de la tabla [medecins],
  • líneas 19-20: el campo «versión» de la clase [Medecin] corresponde al campo [VERSION] (línea 19) de la tabla [medecins]. En este caso, el asistente no reconoce que la columna es, de hecho, una columna de versión que debe incrementarse cada vez que se modifica la fila a la que pertenece. Para asignarle esta función, hay que agregar la anotación @Version. Lo haremos en un paso posterior,
  • líneas 22-23: el campo prenom de la clase [Medecin] corresponde al campo [PRENOM] de la tabla [medecins],
  • líneas 10-11: el campo id corresponde a la clave primaria [ID] de la tabla. Las anotaciones de las líneas 8-9 aclaran este punto,
  • línea 8: la anotación @Id indica que el campo anotado está asociado a la clave primaria de la tabla,
  • línea 9: la capa [JPA] generará la clave primaria de las filas que insertará en la tabla [Medecins]. Existen varias estrategias posibles. En este caso, la estrategia GenerationType.IDENTITY indica que la capa JPA utilizará el modo auto_increment de la tabla MySQL,
  • líneas 25-26: la tabla [creneaux] tiene una clave externa en la tabla [medecins]. Un horario pertenece a un médico. A la inversa, un médico tiene varios horarios asociados a él. Por lo tanto, tenemos una relación de uno (médico) a varios (horarios), una relación calificada por la anotación @OneToMany por JPA (línea 25). El campo de la línea 26 contendrá todos los turnos del médico. Esto sin necesidad de programación. Para comprender completamente la línea 25, debemos presentar la clase [Creneau].

Esta es la siguiente:


package rdvmedecins.jpa;

import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;

@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Column(name = "MDEBUT")
  private int mdebut;

  @Column(name = "HFIN")
  private int hfin;

  @Column(name = "HDEBUT")
  private int hdebut;

  @Column(name = "MFIN")
  private int mfin;

  @Column(name = "VERSION")
  private int version;

  @JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Medecin idMedecin;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "idCreneau")
  private List<Rv> rvList;

// constructores
...
// getters y setters
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    ...
  }
  
}

Solo comentaremos las nuevas anotaciones:

  • Ya mencionamos que la tabla [creneaux] tiene una clave externa hacia la tabla [medecins]: cada horario está asociado a un médico. Se pueden asociar varios horarios al mismo médico. Existe una relación de la tabla [creneaux] hacia la tabla [medecins] que se califica como de varios (turnos) a uno (médico). La anotación @ManyToOne de la línea 32 sirve para definir la clave externa,
  • La línea 31, con la anotación @JoinColumn, especifica la relación de clave externa: la columna [ID_MEDECIN] de la tabla [creneaux] es una clave externa sobre la columna [ID] de la tabla [medecins],
  • línea 33: una referencia al médico titular del horario. Esto también se obtiene sin necesidad de programación.

La relación de clave externa entre la entidad [Creneau] y la entidad [Medecin] se materializa, por lo tanto, mediante dos anotaciones:

  • en la entidad [Creneau]:

@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
  @ManyToOne(optional = false)
private Medecin idMedecin;
  • en la entidad [Medecin]:

@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;

Ambas anotaciones reflejan la misma relación: la de la clave externa de la tabla [creneaux] hacia la tabla [medecins]. Se dice que son inversas entre sí. Solo la relación @ManyToOne es indispensable. Ella define sin ambigüedad la relación de clave externa. La relación @OneToMany es opcional. Si está presente, simplemente hace referencia a la relación @ManyToOne con la que está asociada. Este es el significado del atributo mappedBy de la línea 1 de la entidad [Medecin]. El valor de este atributo es el nombre del campo de la entidad [Creneau] que tiene la anotación @ManyToOne, la cual especifica la clave externa. También en esta misma línea 1 de la entidad [Medecin], el atributo cascade=CascadeType.ALL establece el comportamiento de la entidad [Medecin] con respecto a la entidad [Creneau]:

  • si se inserta una nueva entidad [Medecin] en la base de datos, entonces las entidades [Creneau] del campo de la línea 2 también deben insertarse,
  • si se modifica una entidad [Medecin] en la base de datos, entonces las entidades [Creneau] del campo de la línea 2 también deben modificarse,
  • si se elimina una entidad [Medecin] de la base de datos, entonces también deben eliminarse las entidades [Creneau] del campo de la línea 2.

Presentamos el código de las otras dos entidades sin comentarios particulares, ya que no introducen nuevas notaciones.

La entidad [Client]


package rdvmedecins.jpa;

...
@Entity
@Table(name = "clients")
public class Client implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Column(name = "TITRE")
  private String titre;

  @Column(name = "NOM")
  private String nom;

  @Column(name = "VERSION")
  private int version;

  @Column(name = "PRENOM")
  private String prenom;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "idClient")
  private List<Rv> rvList;

// constructores
...
// getters y setters
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    ...
  }
  
}
  • Las líneas 24 y 25 reflejan la relación de clave externa entre la tabla [rv] y la tabla [clients].

La entidad [Rv]:


package rdvmedecins.jpa;

...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Column(name = "JOUR")
  @Temporal(TemporalType.DATE)
  private Date jour;

  @JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Creneau idCreneau;

  @JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Client idClient;

   // constructores
...

   // getters y setters
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    ...
  }
  
}
  • la línea 13 describe el campo «día» de tipo Java Date. Se indica que en la tabla [rv], la columna [JOUR] (línea 12) es de tipo fecha (sin hora),
  • líneas 16-18: definen la relación de clave externa que tiene la tabla [rv] con la tabla [creneaux],
  • líneas 20-22: definen la relación de clave externa que tiene la tabla [rv] con la tabla [clients].

La generación automática de las entidades JPA nos permite obtener una base de trabajo. A veces es suficiente, otras veces no. Este es el caso aquí:

  • hay que agregar la anotación @Version a los distintos campos de versión de las entidades,
  • hay que escribir métodos toString más explícitos que los generados,
  • las entidades [Medecin] y [Client] son análogas. Las derivaremos de una clase [Personne],
  • vamos a eliminar las relaciones @OneToMany inversas a las relaciones @ManyToOne. No son indispensables y complican la programación,
  • eliminamos la validación @NotNull sobre las claves primarias. Cuando se persiste una entidad JPA con MySQL, la entidad inicial tiene una clave primaria null. Solo después de la persistencia en la base de datos, la clave primaria del elemento persistido tiene un valor.

Con estas especificaciones, las diferentes clases quedan así:

La clase Persona se utiliza para representar a los médicos y a los clientes:


package rdvmedecins.jpa;

import java.io.Serializable;
import javax.persistence.*;

@MappedSuperclass
public class Personne implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID")
  private Long id;

  @Basic(optional = false)
  @Column(name = "TITRE")
  private String titre;

  @Basic(optional = false)
  @Column(name = "NOM")
  private String nom;

  @Basic(optional = false)
  @Column(name = "VERSION")
  @Version
  private int version;
  
  @Basic(optional = false)
  @Column(name = "PRENOM")
  private String prenom;
// constructores
...

// getters y setters
  ...

  @Override
  public String toString() {
    return String.format("[%s,%s,%s,%s,%s]", id, version, titre, prenom, nom);
  }
  
}
  • línea 6: cabe señalar que la clase [Personne] no es en sí misma una entidad (@Entity). Será la clase padre de las entidades. La anotación @MappedSuperClass indica esta situación.

La entidad [Client] encapsula las filas de la tabla [clients]. Deriva de la clase anterior [Personne]:


package rdvmedecins.jpa;

import java.io.Serializable;
import javax.persistence.*;

@Entity
@Table(name = "clients")
public class Client extends Personne implements Serializable {
  private static final long serialVersionUID = 1L;

// constructores
...

  @Override
  public int hashCode() {
...
  }

  @Override
  public boolean equals(Object object) {
  ...
  }

  @Override
  public String toString() {
    return String.format("Client[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
  }
  
}
  • línea 6: la clase [Client] es una entidad JPA,
  • línea 7: está asociada a la tabla [clients],
  • línea 8: deriva de la clase [Personne].

La entidad [Medecin], que encapsula las filas de la tabla [medecins], sigue el mismo patrón:


package rdvmedecins.jpa;

import java.io.Serializable;
import javax.persistence.*;

@Entity
@Table(name = "medecins")
public class Medecin extends Personne implements Serializable {
  private static final long serialVersionUID = 1L;

  // constructores
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    return String.format("Médecin[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
  }
  
}

La entidad [Creneau] encapsula las filas de la tabla [creneaux]:


package rdvmedecins.jpa;

import java.io.Serializable;
import java.util.List;
import javax.persistence.*;

@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {

  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "ID")
  private Long id;
  
  @Basic(optional = false)
  @Column(name = "MDEBUT")
  private int mdebut;
  
  @Basic(optional = false)
  @Column(name = "HFIN")
  private int hfin;
  
  @Basic(optional = false)
  @NotNull
  @Column(name = "HDEBUT")
  private int hdebut;
  
  @Basic(optional = false)
  @Column(name = "MFIN")
  private int mfin;
  
  @Basic(optional = false)
  @Column(name = "VERSION")
  @Version
  private int version;
  
  @JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Medecin medecin;

  // constructores
  ...

  // getters y setters
  ...
 
  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    // TODO: Advertencia: este método no funcionará si los campos de identificación no están definidos
    ...
  }

  @Override
  public String toString() {
    return String.format("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut, hfin, mfin, medecin);
  }
}
  • Las líneas 40-42 modelan la relación «muchos a uno» que existe entre la tabla [creneaux] y la tabla [medecins] de la base de datos: un médico tiene varios horarios, y un horario pertenece a un solo médico.

La entidad [Rv] encapsula las filas de la tabla [rv]:


package rdvmedecins.jpa;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;

@Entity
@Table(name = "rv")
public class Rv implements Serializable {

  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "ID")
  private Long id;
  
  @Basic(optional = false)
  @Column(name = "JOUR")
  @Temporal(TemporalType.DATE)
  private Date jour;
  
  @JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Creneau creneau;
  
  @JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Client client;

   // constructores
...

   // métodos getter y setter
...

  @Override
  public int hashCode() {
    ...
  }

  @Override
  public boolean equals(Object object) {
    ...
  }

  @Override
  public String toString() {
    return String.format("Rv[%s, %s, %s]", id, creneau, client);
  }
}
  • las filas 27-29 modelan la relación «muchos a uno» que existe entre la tabla [rv] y la tabla [clients] (un cliente puede aparecer en varios Rv) de la base de datos, y las líneas 23-25, la relación «muchos a uno» que existe entre la tabla [rv] y la tabla [creneaux] (un horario puede aparecer en varios Rv).

4.4.13. El código de acceso a los datos

Ahora vamos a agregar al proyecto el código de acceso a los datos a través de la capa JPA:

La clase [MainJpql] es la siguiente:


package rdvmedecins.console;

import java.util.Scanner;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class MainJpql {

  public static void main(String[] args) {
    // EntityManagerFactory
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-rdvmedecins-jpql-hibernatePU");
    // entityManager
    EntityManager em = emf.createEntityManager();
    // escáner de teclado
    Scanner clavier = new Scanner(System.in);
    // bucle de entrada de consultas JPQL
    System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
    String requete = clavier.nextLine();
    while (!requete.trim().equals("*")) {
      try {
        // visualización del resultado de la consulta
        for (Object o : em.createQuery(requete).getResultList()) {
          System.out.println(o);
        }
      } catch (Exception e) {
        System.out.println("L'exception suivante s'est produite : " + e);
      }
      // se vacía el contexto de persistencia
      em.clear();
      // nueva consulta
      System.out.println("---------------------------------------------");
      System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
      requete = clavier.nextLine();
    }
    // cierre de recursos
    em.close();
    emf.close();
  }
}
  • línea 12: creación de EntityManagerFactory asociado a la unidad de persistencia que creamos anteriormente. El parámetro del método createEntityManagerFactory es el nombre de esta unidad de persistencia:

  <persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
    ...
</persistence-unit>
  • línea 14: creación del EntityManager que administra la capa de persistencia,
  • línea 19: introducción de una consulta JPQL select,
  • líneas 23-28: visualización del resultado de la consulta,
  • línea 20: la entrada se detiene cuando el usuario escribe *.

Pregunta: indique las consultas JPQL que permiten obtener la siguiente información:


  • lista de médicos en orden descendente por sus nombres
  • lista de médicos cuyo título es «Sr.»
  • lista de los horarios de la Sra. Pelissier
  • lista de citas programadas en orden ascendente por día
  • lista de clientes (nombre) que concertaron una cita con la Sra. PELISSIER el 24/08/2006
  • número de clientes de la Sra. PELISSIER el 24/08/2006
  • Los clientes que no han concertado una cita
  • los médicos que no tienen cita

Nos basaremos en el ejemplo del párrafo 2.7 de [ref1]. A continuación se muestra un ejemplo de ejecución:

Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :
select c from Client c
Hibernate: 
    select
        client0_.ID as ID2_,
        client0_.NOM as NOM2_,
        client0_.PRENOM as PRENOM2_,
        client0_.TITRE as TITRE2_,
        client0_.version as version2_ 
    from
        clients client0_
Client[1,Mr,Jules,MARTIN]
Client[2,Mme,Christine,GERMAN]
Client[3,Mr,Jules,JACQUARD]
Client[4,Melle,Brigitte,BISTROU]
  • línea 2: la consulta JPQL,
  • líneas 3-11: la consulta SQL correspondiente,
  • líneas 12-15: el resultado de la consulta JPQL.

4.5. Relaciones entre el contexto de persistencia y SGBD

4.5.1. La clase Persona

package entites;

...

@Entity
@Table(name = "jpa01_personne")
public class Personne {

  @Id
  @Column(name = "ID", nullable = false)
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Integer id;

  @Column(name = "VERSION", nullable = false)
  @Version
  private int version;

  @Column(name = "NOM", length = 30, nullable = false, unique = true)
  private String nom;

  @Column(name = "PRENOM", length = 30, nullable = false)
  private String prenom;

  @Column(name = "DATENAISSANCE", nullable = false)
  @Temporal(TemporalType.DATE)
  private Date datenaissance;

  @Column(name = "MARIE", nullable = false)
  private boolean marie;

  @Column(name = "NBENFANTS", nullable = false)
  private int nbenfants;

   // constructores

  public Personne() {
  }

  public Personne(String nom, String prenom, Date datenaissance, boolean marie, int nbenfants) {
    setNom(nom);
    setPrenom(prenom);
    setDatenaissance(datenaissance);
    setMarie(marie);
    setNbenfants(nbenfants);
  }

   // toString

  public String toString() {
    return String.format("[%d,%d,%s,%s,%s,%s,%d]", getId(), getVersion(), getNom(), getPrenom(), 
                         new SimpleDateFormat("dd/MM/yyyy").format(getDatenaissance()), isMarie(), getNbenfants());
  }

   // getters y setters
...
}

4.5.2. El programa de prueba

package tests;

....
import entites.Personne;

@SuppressWarnings("unchecked")
public class Test1 {

   // constantes
  private final static String TABLE_NAME = "jpa01_personne";  // Contexto de persistencia
  private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa");
  private static Personne p1;

  public static void main(String[] args) throws Exception {
     // limpieza de la base de datos
    log("clean");
    clean();

     // volcado
    log("dump");
    dump();

     // prueba1
    log("test1");
    test1();

     // prueba2
    log("test2");
    test2();

     // cierre EntityManagerFactory
    emf.close();
  }

   // visualización del contenido de la tabla
  private static void dump() {
     // contexto de persistencia
    EntityManager em = emf.createEntityManager();
     // inicio de transacción
    EntityTransaction tx = em.getTransaction();
    tx.begin();
     // visualización de personas
    for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) {
      System.out.println(p);
    }
     // fin de transacción
    tx.commit();
     // fin de contexto
    em.close();
  }

   // borrado BD
  private static void clean() {
     // contexto de persistencia
    EntityManager em = emf.createEntityManager();
     // inicio de transacción
    EntityTransaction tx = em.getTransaction();
    tx.begin();
     // eliminar los elementos de la tabla PERSONNES
    em.createNativeQuery("delete from " + TABLE_NAME).executeUpdate();
     // fin de transacción
    tx.commit();
     // fin de contexto
    em.close();
  }

   // registros
  private static void log(String message) {
    System.out.println("main : ----------- " + message);
  }

   // gestión de objetos persistentes
  public static void test1() throws ParseException {
     // contexto de persistencia
    EntityManager em = emf.createEntityManager();
     // creación de personas
    p1 = new Personne("Martin", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2);
    Personne p2 = new Personne("Durant", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0);
     // inicio de transacción
    EntityTransaction tx = em.getTransaction();
    System.out.println("début transaction");
    tx.begin();
     // persistencia de personas
     // los registros muestran que la operación SQL INSERT se genera inmediatamente después de la operación de persistencia
     // probablemente para obtener la clave primaria
    System.out.println(String.format("Personne p1 %s non persistée", p1));
    System.out.println("em.persist(p1)");
    em.persist(p1);
    System.out.println(String.format("Personne p1 %s persistée", p1));
     // persona p2
     // INSERT se genera al momento de la operación de persistencia
    System.out.println(String.format("Personne p2 %s non persistée", p2));
    System.out.println("em.persist(p2)");
    em.persist(p2);
    System.out.println(String.format("Personne p2 %s persistée", p2));
    p2.setMarie(true);
    System.out.println(String.format("Personne p2 %s modifiée", p2));
     // la operación DELETE relacionada con la operación de eliminación solo se realiza al final de la transacción
    System.out.println("em.remove(p2)");
    em.remove(p2);
    System.out.println(String.format("Personne p2 %s supprimée", p2));
     // modificación p1
    p1.setNom("P1");
     // fin de la transacción
    System.out.println("fin transaction");
    tx.commit();
     // fin de contexto
    em.close();
     // se muestra la tabla
    dump();
  }

   // gestión de objetos persistentes
  public static void test2() throws ParseException {
     // contexto de persistencia
    EntityManager em = emf.createEntityManager();
     // inicio de transacción
    EntityTransaction tx = em.getTransaction();
    System.out.println("début transaction");
    tx.begin();
     // se modifica la persona p1, que actualmente está desvinculada
    System.out.println(String.format("Personne p1 %s actuelle non persistée", p1));
    p1.setMarie(false);
    System.out.println(String.format("Personne p1 %s nouvelle non persistée", p1));
     // se vuelve a vincular a la persona P1
    System.out.println("em.merge(p1)");
    Personne p1b = em.merge(p1);
    System.out.println(String.format("Personne p1b %s attachée", p1b));
     // fin de la transacción
    System.out.println("fin transaction");
    tx.commit();
       // fin de contexto
    em.close();
   // se muestra la tabla
    dump();
  }
}

4.5.3. La configuración de Hibernate

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
  <persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
         <!- - proveedor -->
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
             <!-- Clases persistentes -->
      <property name="hibernate.archive.autodetection" value="class, hbm" />
      <property name="hibernate.show_sql" value="true"/>
....
             <! -- creación automática del esquema -->
      <property name="hibernate.hbm2ddl.auto" value="create" />
....
    </properties>
  </persistence-unit>
</persistence>

4.5.4. La configuración de log4j.properties

# Enviar mensajes de registro directamente a stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

# Opción de registrador raíz
log4j.rootLogger=ERROR, stdout

# Opciones de registro de Hibernate (INFO solo muestra mensajes de inicio)
#log4j.logger.org.hibernate=DEBUG

# Registrar los argumentos en tiempo de ejecución de los parámetros de enlace de JDBC
log4j.logger.org.hibernate.type=DEBUG

4.5.5. Los resultados

init:
deps-jar:
Compiling 1 source file to C:\data\travail\2008-2009\netbeans\jpa\hibernate-personnes-entites\build\classes
compile-single:
run-single:
main : ----------- limpiar
Hibernate: delete from jpa01_personne
main : ----------- volcado
Hibernate: select personne0_.ID as ID0_, personne0_.DATENAISSANCE as DATENAIS2_0_, personne0_.MARIE as MARIE0_, personne0_.NBENFANTS as NBENFANTS0_, personne0_.NOM as NOM0_, personne0_.PRENOM as PRENOM0_, personne0_.VERSION as VERSION0_ from jpa01_personne personne0_ order by personne0_.NOM asc
main : ----------- prueba1
début transaction
Personne p1 [null,0,Martin,Paul,31/01/2000,true,2] non persistée
em.persist(p1)
Hibernate: insert into jpa01_personne (DATENAISSANCE, MARIE, NBENFANTS, NOM, PRENOM, VERSION) values (?, ?, ?, ?, ?, ?)
17:57:26,312 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: 1
17:57:26,312 DEBUG BooleanType:133 - binding 'true' to parameter: 2
17:57:26,312 DEBUG IntegerType:133 - binding '2' to parameter: 3
17:57:26,312 DEBUG StringType:133 - binding 'Martin' to parameter: 4
17:57:26,312 DEBUG StringType:133 - binding 'Paul' to parameter: 5
17:57:26,312 DEBUG IntegerType:133 - binding '0' to parameter: 6
Personne p1 [1,0,Martin,Paul,31/01/2000,true,2] persistée
Personne p2 [null,0,Durant,Sylvie,05/07/2001,false,0] non persistée
em.persist(p2)
Hibernate: insert into jpa01_personne (DATENAISSANCE, MARIE, NBENFANTS, NOM, PRENOM, VERSION) values (?, ?, ?, ?, ?, ?)
17:57:26,328 DEBUG DateType:133 - binding '05 juillet 2001' to parameter: 1
17:57:26,328 DEBUG BooleanType:133 - binding 'false' to parameter: 2
17:57:26,328 DEBUG IntegerType:133 - binding '0' to parameter: 3
17:57:26,328 DEBUG StringType:133 - binding 'Durant' to parameter: 4
17:57:26,328 DEBUG StringType:133 - binding 'Sylvie' to parameter: 5
17:57:26,328 DEBUG IntegerType:133 - binding '0' to parameter: 6
Personne p2 [2,0,Durant,Sylvie,05/07/2001,false,0] persistée
Personne p2 [2,0,Durant,Sylvie,05/07/2001,true,0] modifiée
em.remove(p2)
Personne p2 [2,0,Durant,Sylvie,05/07/2001,true,0] supprimée
fin transaction
Hibernate: update jpa01_personne set DATENAISSANCE=?, MARIE=?, NBENFANTS=?, NOM=?, PRENOM=?, VERSION=? where ID=? and VERSION=?
17:57:26,343 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: 1
17:57:26,343 DEBUG BooleanType:133 - binding 'true' to parameter: 2
17:57:26,343 DEBUG IntegerType:133 - binding '2' to parameter: 3
17:57:26,343 DEBUG StringType:133 - binding 'P1' to parameter: 4
17:57:26,359 DEBUG StringType:133 - binding 'Paul' to parameter: 5
17:57:26,359 DEBUG IntegerType:133 - binding '1' to parameter: 6
17:57:26,359 DEBUG IntegerType:133 - binding '1' to parameter: 7
17:57:26,359 DEBUG IntegerType:133 - binding '0' to parameter: 8
Hibernate: delete from jpa01_personne where ID=? and VERSION=?
17:57:26,359 DEBUG IntegerType:133 - binding '2' to parameter: 1
17:57:26,359 DEBUG IntegerType:133 - binding '0' to parameter: 2
Hibernate: select personne0_.ID as ID0_, personne0_.DATENAISSANCE as DATENAIS2_0_, personne0_.MARIE as MARIE0_, personne0_.NBENFANTS as NBENFANTS0_, personne0_.NOM as NOM0_, personne0_.PRENOM as PRENOM0_, personne0_.VERSION as VERSION0_ from jpa01_personne personne0_ order by personne0_.NOM asc
17:57:26,375 DEBUG IntegerType:172 - returning '1' as column: ID0_
17:57:26,390 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_
17:57:26,390 DEBUG BooleanType:172 - returning 'true' as column: MARIE0_
17:57:26,390 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_
17:57:26,390 DEBUG StringType:172 - returning 'P1' as column: NOM0_
17:57:26,390 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_
17:57:26,390 DEBUG IntegerType:172 - returning '1' as column: VERSION0_
[1,1,P1,Paul,31/01/2000,true,2]
main : ----------- prueba2
début transaction
Personne p1 [1,1,P1,Paul,31/01/2000,true,2] actuelle non persistée
Personne p1 [1,1,P1,Paul,31/01/2000,false,2] nouvelle non persistée
em.merge(p1)
Hibernate: select personne0_.ID as ID0_0_, personne0_.DATENAISSANCE as DATENAIS2_0_0_, personne0_.MARIE as MARIE0_0_, personne0_.NBENFANTS as NBENFANTS0_0_, personne0_.NOM as NOM0_0_, personne0_.PRENOM as PRENOM0_0_, personne0_.VERSION as VERSION0_0_ from jpa01_personne personne0_ where personne0_.ID=?
17:57:26,406 DEBUG IntegerType:133 - binding '1' to parameter: 1
17:57:26,406 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_0_
17:57:26,406 DEBUG BooleanType:172 - returning 'true' as column: MARIE0_0_
17:57:26,406 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_0_
17:57:26,406 DEBUG StringType:172 - returning 'P1' as column: NOM0_0_
17:57:26,406 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_0_
17:57:26,406 DEBUG IntegerType:172 - returning '1' as column: VERSION0_0_
Personne p1b [1,1,P1,Paul,31/01/2000,false,2] attachée
fin transaction
Hibernate: update jpa01_personne set DATENAISSANCE=?, MARIE=?, NBENFANTS=?, NOM=?, PRENOM=?, VERSION=? where ID=? and VERSION=?
17:57:26,406 DEBUG DateType:133 - binding '31 janvier 2000' to parameter: 1
17:57:26,406 DEBUG BooleanType:133 - binding 'false' to parameter: 2
17:57:26,406 DEBUG IntegerType:133 - binding '2' to parameter: 3
17:57:26,421 DEBUG StringType:133 - binding 'P1' to parameter: 4
17:57:26,421 DEBUG StringType:133 - binding 'Paul' to parameter: 5
17:57:26,421 DEBUG IntegerType:133 - binding '2' to parameter: 6
17:57:26,421 DEBUG IntegerType:133 - binding '1' to parameter: 7
17:57:26,421 DEBUG IntegerType:133 - binding '1' to parameter: 8
Hibernate: select personne0_.ID as ID0_, personne0_.DATENAISSANCE as DATENAIS2_0_, personne0_.MARIE as MARIE0_, personne0_.NBENFANTS as NBENFANTS0_, personne0_.NOM as NOM0_, personne0_.PRENOM as PRENOM0_, personne0_.VERSION as VERSION0_ from jpa01_personne personne0_ order by personne0_.NOM asc
17:57:26,453 DEBUG IntegerType:172 - returning '1' as column: ID0_
17:57:26,453 DEBUG DateType:172 - returning '31 janvier 2000' as column: DATENAIS2_0_
17:57:26,453 DEBUG BooleanType:172 - returning 'false' as column: MARIE0_
17:57:26,453 DEBUG IntegerType:172 - returning '2' as column: NBENFANTS0_
17:57:26,453 DEBUG StringType:172 - returning 'P1' as column: NOM0_
17:57:26,453 DEBUG StringType:172 - returning 'Paul' as column: PRENOM0_
17:57:26,453 DEBUG IntegerType:172 - returning '2' as column: VERSION0_
[1,2,P1,Paul,31/01/2000,false,2]
BUILD SUCCESSFUL (total time: 3 seconds)

Pregunta: Establezca la relación entre el código Java y los resultados mostrados.