5. Versión 1: Arquitectura Spring / JPA
Se propone desarrollar una aplicación de consola y una aplicación gráfica que permitan generar la nómina de las cuidadoras infantiles empleadas por la «Casa de la Primera Infancia» de un municipio. Esta aplicación tendrá la siguiente arquitectura:
![]() |
5.1. BD La base de datos
Los datos estáticos necesarios para elaborar la nómina se almacenarán en una base de datos a la que en adelante nos referiremos como dbpam. Esta base de datos podría contener las siguientes tablas:
Estructura:
clave primaria | |
N.º de versión: aumenta con cada modificación de la fila | |
número de seguro social del empleado – único | |
nombre del empleado | |
su nombre | |
su dirección | |
su ciudad | |
su código postal | |
clave externa en el campo [ID] de la tabla [INDEMNITES] |
Su contenido podría ser el siguiente:

Estructura:
clave primaria | |
N.º de versión: aumenta con cada modificación de la línea | |
porcentaje: contribución social generalizada + contribución al pago de la deuda social | |
porcentaje: contribución social generalizada deducible | |
porcentaje: seguridad social, viudedad, vejez | |
porcentaje: pensión complementaria + seguro de desempleo |
Su contenido podría ser el siguiente:
![]()
Las tasas de las cotizaciones sociales son independientes del empleado. La tabla anterior solo tiene una fila.
clave primaria | ||
N.º de versión: aumenta con cada modificación de la línea | ||
índice de procesamiento: único | ||
precio neto en euros por una hora de guardia | ||
indemnización de manutención en euros por día de custodia | ||
indemnización por comida en euros por día de cuidado | ||
Indemnización por vacaciones pagadas. Es un porcentaje que se aplica al salario base. | ||
Su contenido podría ser el siguiente:

Cabe señalar que las indemnizaciones pueden variar de una cuidadora a otra. De hecho, están vinculadas a una cuidadora específica a través de su índice salarial. Así, la Sra. Marie Jouveinal, que tiene un índice salarial de 2 (tabla EMPLOYES), tiene un salario por hora de 2,1 euros (tabla INDEMNITES).
5.2. Método de cálculo del salario de una cuidadora infantil
A continuación, presentamos el método de cálculo del salario mensual de una cuidadora infantil. No pretende ser el que se utiliza en la realidad. Tomamos como ejemplo el salario de la Sra. Marie Jouveinal, quien trabajó 150 horas durante 20 días en el mes a pagar.
Se toman en cuenta los siguientes elementos: | | |
El salario base de la niñera se calcula mediante la siguiente fórmula: | ||
Se deben deducir ciertas cotizaciones sociales deben deducirse de este salario base: | | |
Total de contribuciones sociales: | ||
Además, la cuidadora infantil tiene derecho, por cada día trabajado, a una indemnización por gastos de manutención y a una indemnización por comida. Por este concepto, recibe las siguientes indemnizaciones: | | |
En definitiva, el salario neto que se le debe pagar a la cuidadora infantil es el siguiente: |
5.3. Funcionamiento de la aplicación de consola
A continuación se muestra un ejemplo de cómo ejecutar la aplicación de consola en una ventana de DOS:
Escribiremos un programa que recibirá la siguiente información:
- número de seguro social de la cuidadora (254104940426058 en el ejemplo —línea 1—)
- número total de horas trabajadas (150 en el ejemplo —línea 1)
- número total de días trabajados (20 en el ejemplo —línea 1)
Se observa que:
- líneas 9-14: muestran la información sobre la empleada cuyo número de seguro social se ha proporcionado
- líneas 17-20: muestran las tasas de las diferentes cotizaciones
- líneas 23-26: muestran las prestaciones asociadas al índice salarial del empleado (en este caso, el índice 2)
- líneas 29-33: muestran los componentes del salario a pagar
La aplicación señala los posibles errores:
Llamada sin parámetros:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar
Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés
Llamada con datos erróneos:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150x 20x
Le nombre d'heures travaillées [150x] est erroné
Le nombre de jours travaillés [20x] est erroné
Llamada con un número de seguro social incorrecto:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar xx 150 20
L'erreur suivante s'est produite : L'employé de n°[xx] est introuvable
5.4. Funcionamiento de la aplicación gráfica
La aplicación gráfica permite calcular los salarios de las cuidadoras infantiles mediante un formulario Swing:
![]() |
- La información que antes se pasaba como parámetros al programa de consola, ahora se ingresa mediante los campos de entrada [1, 2, 3].
- El botón [4] inicia el cálculo del salario
- El formulario muestra los distintos componentes del salario hasta llegar al salario neto a pagar [5]
La lista desplegable [1, 6] no muestra los números de identificación SS de los empleados, sino sus nombres y apellidos. Aquí se parte de la hipótesis de que no hay dos empleados con el mismo nombre y apellido.
5.5. Creación de la base de datos
Ejecutamos WampServer y utilizamos la herramienta PhpMyAdmin [1]:
![]() |
- en [2], seleccionamos la opción [Bases de données],
![]() |
- en [3], se crea una base de datos [dbpam_hibernate],
- en [4], la base de datos creada. La seleccionamos,
![]() |
- en [5], queremos importar un script SQL,
- en [6], usamos el botón [Parcourir] para seleccionar el archivo,
![]() |
- en [7,8], seleccionamos el script SQL,
- en [9], se ejecuta,
![]() |
- en [10], se han creado las tablas. Su contenido es el siguiente:
tabla EMPLOYES

tabla INDEMNITES

tabla
tabla COTISATIONS
![]()
5.6. Implementación JPA
5.6.1. Capa JPA / Hibernate
Vamos a configurar la capa JPA en el siguiente entorno:
![]() |
Un programa de consola trabajará con la base de datos. Para ello, es necesario:
- tener una base de datos,
- tener el controlador JDBC del SGBD, en este caso MySQL,
- implementar la capa JPA con Hibernate,
- escribir el programa de consola.
Creamos el proyecto Maven [mv-pam-jpa-hibernate] [1]:
![]() |
En la arquitectura de nuestra aplicación necesitamos los siguientes elementos:
- la base de datos,
- el controlador JDBC de SGBD MySQL,
- la capa JPA / Hibernate (entidades y configuración),
- el programa de consola de prueba.
5.6.1.1. La base de datos
En primer lugar, creemos la base de datos vacía. Ejecutamos WampServer y utilizamos la herramienta PhpMyAdmin [1]:
![]() |
- en [2], seleccionamos la opción [Bases de données],
![]() |
- en [3], se crea una base de datos [dbpam_hibernate],
- en [4], la base de datos creada.
5.6.1.2. Configuración de la capa JPA
La conexión entre la capa JDBC y la base de datos se realiza en el archivo [persistence.xml], que configura la capa JPA. Este archivo se puede crear con NetBeans:
![]() |
- en la pestaña [services] [1], nos conectamos a la base de datos con el controlador JDBC de MySQL [2],
- en [3], el nombre de la base de datos a la que se desea conectarse.
- en [4], el nombre de la base de datos,
- en [5], nos conectamos como root sin contraseña,
- en [6], se puede probar la conexión,
- en [7], la conexión se ha realizado con éxito.
![]() |
- la conexión aparece en [8] y en [9],
- en [10], se agrega un nuevo elemento al proyecto,
![]() |
- en [11] se selecciona la categoría [Persistence] y en [12] el elemento [Persistence Unit],
- en [13], se le da un nombre a esta unidad de persistencia,
- en [14], se elige una implementación de Hibernate,
- en [15], se designa la conexión que acabamos de crear con la base de datos MySQL,
- en [16], se indica que, al instanciar la capa JPA, esta debe crear las tablas correspondientes a las entidades JPA del proyecto.
Al finalizar el asistente, se genera el archivo [persistence.xml]:
![]() |
- el archivo aparece en una nueva rama del proyecto, en una carpeta [META-INF] [1],
- que corresponde a la carpeta [src/main/resources] del proyecto [2,3].
Su contenido 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-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<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.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
- línea 3: el nombre de la unidad de persistencia y el tipo de transacciones. RESOURCE_LOCAL indica que el proyecto gestiona las transacciones por sí mismo. En este caso, será el programa de consola el que deberá hacerlo,
- línea 4: la implementación JPA utilizada es Hibernate,
- líneas 6-9: las características JDBC de la conexión a la base de datos,
- línea 11: solicita la creación de las tablas correspondientes a las entidades JPA. De hecho, NetBeans genera aquí una configuración errónea. La configuración debe ser la siguiente:
<property name="hibernate.hbm2ddl.auto" value="create"/>
Con la opción «create», Hibernate, al instanciar la capa JPA, elimina y luego crea las tablas correspondientes a las entidades JPA. La opción «create-drop» hace lo mismo, pero al final del ciclo de vida de la capa JPA, elimina todas las tablas. Existe otra opción:
<property name="hibernate.hbm2ddl.auto" value="update"/>
Esta opción crea las tablas si no existen, pero no las elimina si ya existen.
Agregaremos otras tres propiedades a la configuración de Hibernate:
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
Estas le indican a Hibernate que muestre las órdenes SQL que envía a la base de datos. El archivo completo es, por lo tanto, 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-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<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.hbm2ddl.auto" value="create"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
</properties>
</persistence-unit>
</persistence>
5.6.1.3. Las dependencias
Volvamos a la arquitectura del proyecto:
![]() |
Hemos configurado la capa JPA mediante el archivo [persistence.xml]. La implementación elegida fue Hibernate. Esto generó algunas dependencias en el proyecto:
![]() |
Estas dependencias se deben a la inclusión de Hibernate en el proyecto. Necesitamos agregar otra dependencia: el controlador JDBC de MySQL, que implementa la capa JDBC de la arquitectura. Modificamos el archivo [pom.xml] de la siguiente manera:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
</dependency>
...
<dependency>
<groupId>org.hibernate.common</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>4.0.1.Final</version>
</dependency>
</dependencies>
Las líneas 8 a 12 agregan la dependencia del controlador JDBC de MySQL.
5.6.1.4. Las entidades JPA
![]() |
Pregunta: Siguiendo el procedimiento del ejemplo del párrafo 4.4, genere las entidades [Cotisation, Indemnite, Employe].
Notas:
- las entidades formarán parte de un paquete denominado [jpa],
- cada entidad tendrá un número de versión,
- si dos entidades están vinculadas por una relación, solo se creará la relación principal @ManyToOne. La relación inversa @OneToMany no se creará.
5.6.1.5. El código de la clase principal
Incluimos en el proyecto las entidades JPA y [1] desarrolladas anteriormente:
![]() |
y luego agregamos [2], la siguiente clase [main.Main]:
package main;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class Main {
public static void main(String[] args) {
// basta con crear el Entity Manager para construir la capa JPA
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpa-hibernatePU");
EntityManager em=emf.createEntityManager();
// liberación de recursos
em.close();
emf.close();
}
}
- línea 10: creamos la clase EntityManagerFactory de la unidad de persistencia denominada [mv-pam-jpa-hibernatePU]. Este nombre proviene del archivo [persistence.xml]:
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
...
</persistence-unit>
- línea 12: se crea el archivo EntityManager. Esta creación genera la capa JPA. Se procesará el archivo [persistence.xml] y, por lo tanto, se crearán las tablas de la base de datos,
- líneas 14-15: se liberan los recursos.
5.6.1.6. Tests
Volvamos a la arquitectura de nuestro proyecto:
![]() |
Se han implementado todas las capas. Ejecutamos el proyecto [2].
![]() |
Los resultados en la consola son los siguientes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
En la consola solo se encuentran registros de Hibernate, ya que el programa ejecutado no hace nada más que instanciar la capa JPA. Cabe destacar los siguientes puntos:
- línea 43: Hibernate intenta eliminar la clave externa de la tabla [EMPLOYES],
- líneas 51-55: eliminación de las tres tablas,
- línea 57: creación de la tabla [COTISATIONS],
- línea 67: creación de la tabla [EMPLOYES],
- línea 80: creación de la tabla [INDEMNITES],
- línea 91: creación de la clave externa de la tabla [EMPLOYES].
En NetBeans, se pueden ver las tablas en la conexión que se creó anteriormente:
![]() |
Las tablas creadas dependen tanto de la implementación de la capa JPA utilizada como de la de SGBD. Por lo tanto, una implementación de JPA / EclipseLink con la misma base de datos puede generar tablas diferentes. Eso es lo que veremos ahora.
5.6.2. Capa JPA / EclipseLink
Vamos a crear un nuevo proyecto Maven en el siguiente entorno:
![]() |
Seguiremos los pasos del párrafo anterior:
- crear una base de datos MySQL [dbpam_eclipselink]. Usaremos el script [dbpam_eclipselink.sql] para generarla,
- crearemos el archivo [persistence.xml] del proyecto. Tomaremos la implementación JPA 2.0 EclipseLink,
- agregar en las dependencias generadas la dependencia del controlador JDBC de MySQL,
- agregar las entidades JPA y el programa de consola,
- realizar las pruebas.
El archivo [persistence.xml] quedará así:
<?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="pam-jpa-eclipselinkPU" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="eclipselink.target-database" value="MySQL"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
<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="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
- las propiedades 9-13 fueron generadas por el asistente de NetBeans,
- línea 14: esta propiedad nos permite establecer el nivel de registro de EclipseLink. El nivel de FINE nos permite conocer las órdenes de SQL que EclipseLink emitirá a la base de datos,
- línea 15: al instanciar la capa JPA / EclipseLink, las tablas de las entidades JPA se eliminarán y luego se volverán a crear.
Los resultados obtenidos en la consola son los siguientes:
- líneas 26-30: conexión a la base de datos MySQL,
- líneas 31-34: confirmación de que la conexión se realizó con éxito,
- línea 36: eliminación de la clave externa de la tabla [EMPLOYES],
- línea 37: eliminación de la tabla [COTISATIONS],
- línea 38: creación de la tabla [COTISATIONS]. Cabe destacar que la clave primaria ID no tiene el atributo MySQL auto_increment. Esto significa que no es MySQL la que genera los valores de la clave primaria,
- línea 39: eliminación de la tabla [EMPLOYES],
- línea 40: creación de la tabla [EMPLOYES]. Su clave primaria ID no tiene el atributo MySQL auto_increment,
- línea 41: eliminación de la tabla [INDEMNITES],
- línea 42: creación de la tabla [INDEMNITES]. Su clave primaria ID no tiene el atributo MySQL auto_increment,
- línea 43: creación de la clave externa de la tabla [EMPLOYES] hacia la tabla [INDEMNITES],
- línea 44: creación de una tabla [SEQUENCE]. Se utilizará para generar las claves primarias de las tres tablas anteriores,
- línea 47: se produce una excepción porque esta tabla ya existía,
- líneas 51-53: inicialización de la tabla [SEQUENCE].
La existencia de las tablas generadas se puede verificar en NetBeans [1]:
![]() |
Por lo tanto, a partir de las mismas entidades JPA, las implementaciones JPA, Hibernate y EclipseLink no generan las mismas tablas. En el resto del documento, cuando la implementación JPA utilizada sea:
- Hibernate, se utilizará la base de datos [dbpam_hibernate];
- EclipseLink, se utilizará la base de datos [dbpam_eclipselink].
5.6.3. Tarea a realizar
Siguiendo el mismo procedimiento que antes,
- crea y prueba un proyecto [mv-pam-jpa-hibernate-oracle] utilizando una implementación JPA de Hibernate y una SGBD de Oracle,
- crear y probar un proyecto [mv-pam-jpa-hibernate-mssql] utilizando una implementación JPA de Hibernate y un servidor SGBD SQL,
- crear y probar un proyecto [mv-pam-jpa-eclipselink-oracle] utilizando una implementación de JPA EclipseLink y un servidor Oracle SGBD,
- crear y probar un proyecto [mv-pam-jpa-eclipselink-mssql] utilizando una implementación JPA EclipseLink y un servidor SGBD SQL,
5.6.4. ¿Lazy o Eager?
Volvamos a una posible definición de la entidad [Employe]:
package jpa;
...
@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
@Column(name="VERSION",nullable=false)
private int version;
@Column(name="SS", nullable=false, unique=true, length=15)
private String SS;
@Column(name="NOM", nullable=false, length=30)
private String nom;
@Column(name="PRENOM", nullable=false, length=20)
private String prenom;
@Column(name="ADRESSE", nullable=false, length=50)
private String adresse;
@Column(name="VILLE", nullable=false, length=30)
private String ville;
@Column(name="CP", nullable=false, length=5)
private String codePostal;
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
...
}
Las líneas 27 a 29 definen la clave externa de la tabla [EMPLOYES] hacia la tabla [INDEMNITES]. El atributo fetch de la línea 27 define la estrategia de búsqueda del campo indemnite de la línea 29. Existen dos modos:
- FetchType.LAZY: cuando se busca a un empleado, no se recupera la indemnización que le corresponde. Esta se recuperará cuando se haga referencia por primera vez al campo [Employe].indemnite.
- FetchType.EAGER: cuando se busca a un empleado, se muestra la indemnización que le corresponde. Este es el modo predeterminado cuando no se especifica ningún modo.
Para entender la utilidad de la opción FetchType.LAZY, podemos tomar el siguiente ejemplo. En una página web se presenta una lista de empleados sin las indemnizaciones mediante un enlace [Details]. Al hacer clic en este enlace, se muestran las indemnizaciones del empleado seleccionado. Se observa que:
- para mostrar la primera página no se necesita la información de los empleados con sus prestaciones. En ese caso, el modo FetchType.LAZY es adecuado;
- para mostrar la segunda página con los detalles, se debe realizar una consulta adicional a la base de datos para obtener las prestaciones del empleado seleccionado.
El modo FetchType.LAZY evita traer demasiados datos que la aplicación no necesita de inmediato. Veamos un ejemplo.
El proyecto [mv-pam-jpa-hibernate] se duplica:
![]() |
- en [1], se copia el proyecto,
- en [2], se indica la carpeta de la copia y en [3] su nombre,
- en [4], el nuevo proyecto tiene el mismo nombre que el anterior. Modificamos esto:
![]() |
- en [1], renombramos el proyecto,
- a [2], renombramos el proyecto y su artifactId,
- a [3], el nuevo proyecto.
Modificamos el programa [Main.java] de la siguiente manera:
package main;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import jpa.Employe;
public class Main {
// la consulta JPQL que se muestra a continuación devuelve un empleado
// la clave externa [Employe].indemnite se encuentra en FetchType.LAZY
public static void main(String[] args) {
// basta con crear el Entity Manager para construir la capa JPA
EntityManagerFactory emf = Persistence.createEntityManagerFactory("pam-jpa-hibernatePU");
// primer intento
EntityManager em = emf.createEntityManager();
Employe employe = (Employe) em.createQuery("select e from Employe e where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
em.close();
// se muestra el empleado
try {
System.out.println(employe);
} catch (Exception ex) {
System.out.println(ex);
}
// segundo intento
em = emf.createEntityManager();
employe = (Employe) em.createQuery("select e from Employe e left join fetch e.indemnite where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
// liberar los recursos
em.close();
// se muestra el empleado
try {
System.out.println(employe);
} catch (Exception ex) {
System.out.println(ex);
}
// liberación de recursos
emf.close();
}
}
- línea 15: creamos el EntityManagerFactory a partir de la capa JPA,
- línea 17: obtenemos el EntityManager, que nos permite interactuar con la capa JPA,
- línea 18: se solicita el empleado con nombre Jouveinal,
- línea 19: se cierra el EntityManager. Esto tiene como efecto cerrar el contexto de persistencia.
- línea 22: se muestra el empleado recibido.
La clase [Employe] es la siguiente:
package jpa;
...
@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
@Column(name="VERSION",nullable=false)
private int version;
@Column(name="SS", nullable=false, unique=true, length=15)
private String SS;
@Column(name="NOM", nullable=false, length=30)
private String nom;
@Column(name="PRENOM", nullable=false, length=20)
private String prenom;
@Column(name="ADRESSE", nullable=false, length=50)
private String adresse;
@Column(name="VILLE", nullable=false, length=30)
private String ville;
@Column(name="CP", nullable=false, length=5)
private String codePostal;
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
/**
* Returns a string representation of the object. This implementation constructs
* that representation based on the id fields.
* @return a string representation of the object.
*/
@Override
public String toString() {
return "jpa.Employe[id=" + getId()
+ ",version="+getVersion()
+",SS="+getSS()
+ ",nom="+getNom()
+ ",prenom="+getPrenom()
+ ",adresse="+getAdresse()
+",ville="+getVille()
+",code postal="+getCodePostal()
+",indice="+getIndemnite().getIndice()
+"]";
}
...
}
- línea 27: el campo indemnite se vuelve a poner en modo LAZY,
- línea 47: utiliza el campo indemnite. Si se invoca el método toString cuando el campo indemnite aún no ha sido restablecido, se restablecerá en ese momento. A menos que el contexto de persistencia se haya cerrado, como en el ejemplo.
Volvamos al código de [Main]:
- líneas 21-25: debería producirse una excepción. De hecho, se llamará al método toString. Este utilizará el campo indemnite. Se buscará dicho campo. Como el contexto de persistencia se ha cerrado, la entidad [Employe] recuperada ya no existe, de ahí la excepción.
- línea 27: se crea un nuevo EntityManager,
- línea 28: se solicita el empleado Jouveinal, indicando explícitamente en la consulta JPQL la indemnización correspondiente. Esta solicitud explícita es necesaria porque el modo de búsqueda de esta indemnización es LAZY,
- línea 30: se cierra el EntityManager,
- líneas 32-36: se vuelve a mostrar al empleado. No debería haber ninguna excepción.
Para ejecutar el proyecto, se necesita una base de datos completa. Se creará siguiendo los pasos del párrafo 5.5. Además, se debe modificar el archivo [persistence.xml]:
<?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-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<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>
- se ha eliminado la opción que creaba las tablas. La base de datos ya existe y está completa,
- se han eliminado las opciones que hacían que Hibernate registrara las órdenes SQL que enviaba a la base de datos.
Al ejecutar el proyecto, aparecen los siguientes dos mensajes en la consola:
- línea 1: la excepción que se produjo al intentar buscar la indemnización que faltaba, ya que la sesión estaba cerrada. Se observa que la indemnización no se había recuperado debido al modo LAZY,
- línea 2: el empleado con su indemnización obtenida mediante una consulta que eludió el modo LAZY.
5.6.5. Tarea a realizar
Siguiendo un procedimiento similar al que acabamos de seguir, crea un proyecto [mv-pam-pa-eclipselink-lazy] que muestre el comportamiento de EclipseLink en comparación con el modo LAZY.
Se obtienen los siguientes resultados:
En el modo LAZY, ambas consultas devolvieron la indemnización junto con el empleado. Al buscar información en Internet sobre esta anomalía, se descubre que la anotación [FetchType.LAZY] (línea 1):
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
no es una orden, sino una sugerencia. El implementador JPA no está obligado a seguirla. Así pues, se observa que el código a veces depende de la implementación JPA utilizada. Es posible configurar EclipseLink para que se comporte como se espera en el modo LAZY.
5.6.6. Para continuar
La arquitectura de la aplicación que se va a desarrollar es la siguiente:
![]() |
Para el resto del documento, se duplicará el proyecto Maven [mv-pam-jpa-hibernate] en el proyecto [mv-pam-spring-hibernate] [1, 2, 3]:
![]() |
- luego renombraremos el nuevo proyecto como [4, 5, 6].
Modificaremos las dependencias del nuevo proyecto. El archivo [pom.xml] queda así:
<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-pam-spring-hibernate</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-spring-hibernate</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>swing-layout</id>
<layout>default</layout>
<name>Repository for library Library[swing-layout]</name>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
</project>
- líneas 25-31: la dependencia para las pruebas JUnit,
- líneas 32-41: las dependencias para el grupo de conexiones de Apache DBCP,
- líneas 42-65: las dependencias para el marco Spring,
- líneas 67-71: las dependencias para la implementación JPA / Hibernate,
- líneas 72-76: la dependencia del controlador JDBC de MySQL,
- líneas 77-81: la dependencia de la interfaz Swing. NetBeans la agrega automáticamente al añadir una interfaz Swing al proyecto.
Además, se generarán las dos bases MySQL:
- [dbpam_hibernate] a partir del script [dbpam_hibernate.sql],
- [dbpam_eclipselink] a partir del script [dbpam_eclipselink.sql],
5.7. Las interfaces de de las capas [metier] y [DAO]
Volvamos a la arquitectura de la aplicación:
![]() |
En la arquitectura anterior, ¿qué interfaz debe ofrecer la capa [DAO] a la capa [metier] y qué interfaz debe ofrecer la capa [metier] a la capa [ui]? Un primer enfoque para definir las interfaces de las diferentes capas consiste en examinar los distintos casos de uso (use cases) de la aplicación. En este caso, tenemos dos, según la interfaz de usuario elegida: consola o formulario gráfico.
Analicemos el modo de uso de la aplicación de consola:
La aplicación recibe tres datos del usuario (véase la línea 1 más arriba)
- el número de seguro social de la cuidadora
- el número de horas trabajadas en el mes
- el número de días trabajados en el mes
A partir de esta información y de otros datos almacenados en archivos de configuración, la aplicación muestra la siguiente información:
- líneas 4-6: los valores ingresados
- líneas 8-10: la información relacionada con el empleado cuyo número de seguro social se ha ingresado
- líneas 12-14: las tasas de las diferentes cotizaciones sociales
- líneas 16-17: las diferentes indemnizaciones pagadas a la cuidadora infantil
- líneas 19-24: los datos de la nómina de la cuidadora infantil
La capa [metier] debe proporcionar cierta información a la capa [ui]:
- la información relacionada con una cuidadora infantil identificada por su número de seguro social. Esta información se encuentra en la tabla [EMPLOYES]. Esto permite mostrar las líneas 6-8.
- los montos de las diversas tasas de cotizaciones sociales que se deben deducir del salario bruto. Esta información se encuentra en la tabla [COTISATIONS]. Esto permite mostrar las líneas 10 a 12.
- los montos de las diversas indemnizaciones relacionadas con la función de cuidadora infantil. Esta información se encuentra en la tabla [INDEMNITES]. Esto permite mostrar las líneas 14 y 15.
- los componentes del salario que se muestran en las líneas 18 a 22.
A partir de esto, se podría decidir realizar un primer registro de la interfaz [IMetier], presentada por la capa [metier] a la capa [ui]:
- línea 1: los elementos de la capa [metier] se colocan en el paquete [metier]
- línea 5: el método [ calculerFeuilleSalaire ] toma como parámetros los tres datos obtenidos por la capa [ui] y devuelve un objeto de tipo [FeuilleSalaire] que contiene la información que la capa [ui] mostrará en la consola. La clase [FeuilleSalaire] podría ser la siguiente:
- línea 9: el empleado al que se refiere la nómina —información n.º 1 mostrada por la capa [ui]
- línea 10: las diferentes tasas de cotización —información n.º 2 mostrada por la capa [ui]
- línea 11: las diferentes indemnizaciones relacionadas con el índice del empleado —información n.º 3 mostrada por la capa [ui]
- línea 12: los componentes de su salario —información n.º 4 mostrada por la capa [ui]
Un segundo caso de uso de la capa [métier] aparece con la interfaz gráfica:
![]() |
Como se ve arriba, la lista desplegable [1, 2] muestra a todos los empleados. Esta lista debe solicitarse a la capa [métier]. La inter ace de esta capa evoluciona entonces de la siguiente manera:
- línea [10]: el método que permitirá a la capa [ui] solicitar la lista de todos los empleados a la capa [métier].
La capa [metier] solo puede inicializar los campos [Employe, Cotisation, Indemnite] del objeto [FeuilleSalaire] mencionado anteriormente consultando la capa [DAO], ya que esta información se encuentra en las tablas de la base de datos. Lo mismo ocurre para obtener la lista de todos los empleados. Se podría crear una única interfaz [DAO] que administre el acceso a las tres entidades [Employe, Cotisation, Indemnite]. Sin embargo, en este caso decidimos crear una interfaz [DAO] por cada entidad.
La interfaz [DAO] para acceder a las entidades [Cotisation] de la tabla [COTISATIONS] será la siguiente:
- En la línea 6, la interfaz [ICotisationDao] gestiona el acceso a la entidad [Cotisation] y, por lo tanto, a la tabla [COTISATIONS] de la base de datos. Nuestra aplicación solo necesita el método [findAll] de la línea 16, que permite recuperar todo el contenido de la tabla [COTISATIONS]. Aquí hemos querido plantearnos un caso más general en el que todas las operaciones CRUD (Crear, Leer, Actualizar, Eliminar) se realizan sobre la entidad.
- línea 8: el método [create] crea una nueva entidad [Cotisation]
- línea 10: el método [edit] modifica una entidad [Cotisation] existente
- línea 12: el método [destroy] elimina una entidad [Cotisation] existente
- línea 14: el método [find] permite recuperar una entidad [Cotisation] existente mediante su identificador id
- línea 16: el método [findAll] devuelve en una lista todas las entidades [Cotisation] existentes
Analicemos la firma del método [create]:
El método create tiene un parámetro cotisation de tipo Cotisation. El parámetro cotisation debe persistir, c.a.d. que se almacena aquí en la tabla [COTISATIONS]. Antes de esta persistencia, el parámetro cotisation tiene un identificador id sin valor. Después de la persistencia, el campo id tiene un valor que es la clave primaria del registro agregado a la tabla [COTISATIONS]. Por lo tanto, el parámetro cotisation es un parámetro de entrada/salida del método create. No parece necesario que el método create devuelva además el parámetro cotisation como resultado. Dado que el método que realiza la llamada tiene una referencia al objeto [Cotisation cotisation], si este se modifica, tiene acceso al objeto modificado, ya que cuenta con una referencia sobre él. Por lo tanto, puede conocer el valor que el método create le ha asignado al campo id del objeto [Cotisation cotisation]. La firma del método podría ser, por lo tanto, más sencilla:
Al escribir una interfaz, es importante recordar que puede utilizarse en dos contextos diferentes: local y distant. En el contexto local, tanto el método que llama como el método al que se llama se ejecutan en el mismo JVM:
![]() |
Si la capa [metier] invoca el método create de la capa [DAO], sí cuenta con una referencia al parámetro [Cotisation cotisation] que pasa al método.
En el contexto distant, el método que llama y el método llamado se ejecutan en diferentes JVM:
![]() |
En el ejemplo anterior, la capa [metier] se ejecuta en la JVM 1 y la capa [DAO] en la JVM 2, en dos máquinas diferentes. Las dos capas no se comunican directamente. Entre ellas se intercala una capa a la que llamaremos capa de comunicación [1]. Esta está compuesta por una capa de transmisión [2] y una capa de recepción [3]. Por lo general, el desarrollador no tiene que escribir estas capas de comunicación. Son generadas automáticamente por herramientas de software. La capa [metier] se escribe como si se ejecutara en la misma JVM que la capa [DAO]. Por lo tanto, no hay ninguna modificación en el código.
El mecanismo de comunicación entre la capa [metier] y la capa [DAO] es el siguiente:
- la capa [metier] invoca el método create de la capa [DAO], pasándole el parámetro [Cotisation cotisation1]
- Este parámetro se pasa, de hecho, a la capa de emisión [2]. Esta transmitirá por la red el valor del parámetro cotisation1 y no su referencia. La forma exacta de este valor depende del protocolo de comunicación utilizado.
- La capa de recepción [3] recuperará este valor y, a partir de él, reconstruirá un objeto [Cotisation cotisation2] que es una réplica del parámetro inicial enviado por la capa [metier]. Ahora tenemos dos objetos idénticos (en cuanto al contenido) en dos JVM diferentes: cotisation1 y cotisation2.
- La capa de recepción pasará el objeto cotisation2 al método create de la capa [DAO], que lo almacenará en la base de datos. Tras esta operación, el campo id del objeto cotisation2 se ha inicializado con la clave primaria del registro agregado a la tabla [COTISATIONS]. Este no es el caso del objeto cotisation1, al que la capa [metier] hace referencia. Si queremos que la capa [metier] tenga una referencia al objeto cotisation2, debemos enviársela. Por lo tanto, debemos modificar la firma del método create de la capa [DAO]:
- Con esta nueva firma, el método create devolverá como resultado el objeto persistente cotisation2. Este resultado se devuelve a la capa de recepción [3], que había llamado a la capa [DAO]. Esta, a su vez, devolverá el valor (y no la referencia) de cotisation2 a la capa de emisión [2].
- La capa de emisión [2] recuperará este valor y, a partir de él, reconstruirá un objeto [Cotisation cotisation3] que representa el resultado devuelto por el método create de la capa [DAO].
- El objeto [Cotisation cotisation3] se devuelve al método de la capa [metier], cuya llamada al método create de la capa [DAO] había iniciado todo este mecanismo. Por lo tanto, la capa [metier] puede conocer el valor de la clave primaria asignada al objeto [Cotisation cotisation1], cuya persistencia había solicitado: es el valor del campo id de cotisation3.
La arquitectura anterior no es la más común. Es más frecuente encontrar las capas [metier] y [DAO] dentro de la misma JVM:
![]() |
En esta arquitectura, son los métodos de la capa [metier] los que deben devolver resultados, y no los de la capa [DAO]. Sin embargo, la siguiente firma del método create de la capa [DAO]:
nos permite no hacer suposiciones sobre la arquitectura realmente implementada. Utilizar firmas que funcionen independientemente de la arquitectura elegida, ya sea local o remota, implica que, en caso de que un método invocado modifique algunos de sus parámetros:
- estos también deben formar parte del resultado del método invocado
- el método que realiza la llamada debe utilizar el resultado del método invocado y no las referencias de los parámetros modificados que transmitió al método invocado.
De esta manera, nos reservamos la posibilidad de pasar de una arquitectura locale a una arquitectura distante sin necesidad de modificar el código. Revisemos, a la luz de esto, la interfaz [ICotisationDao]:
- línea 8: se ha tratado el caso del método create
- línea 10: el método edit utiliza su parámetro [Cotisation cotisation1] para actualizar el registro de la tabla [COTISATIONS] que tiene la misma clave primaria que el objeto cotisation. Devuelve como resultado el objeto cotisation2, que representa el registro modificado. El parámetro cotisation1, por su parte, no se modifica. El método debe devolver cotisation2 como resultado, independientemente de si se trata de una arquitectura distante o locale.
- línea 12: el método destroy elimina el registro de la tabla [COTISATIONS] que tiene la misma clave primaria que el objeto cotisation pasado como parámetro. Este último no se modifica. Por lo tanto, no es necesario devolverlo.
- línea 14: el parámetro id del método find no es modificado por el método. No debe formar parte del resultado.
- línea 16: el método findAll no tiene parámetros. Por lo tanto, no es necesario analizarlo.
En definitiva, solo la firma del método create debe adaptarse para que sea utilizable en el marco de una arquitectura distante. Los razonamientos anteriores serán válidos para las demás interfaces [DAO]. No las repetiremos y utilizaremos directamente firmas que se puedan emplear tanto en el marco de una arquitectura distante como en el de una arquitectura locale.
La interfaz [DAO] para acceder a las entidades [Indemnite] de la tabla [INDEMNITES] será la siguiente:
- En la línea 6, la interfaz [IIndemniteDao] administra el acceso a la entidad [Indemnite] y, por lo tanto, a la tabla [INDEMNITES] de la base de datos. Nuestra aplicación solo necesita el método [findAll] de la línea 16, que permite recuperar todo el contenido de la tabla [INDEMNITES]. Aquí hemos querido plantearnos un caso más general en el que todas las operaciones CRUD (Crear, Leer, Actualizar, Eliminar) se realizan sobre la entidad.
- línea 8: el método [create] crea una nueva entidad [Indemnite]
- línea 10: el método [edit] modifica una entidad [Indemnite] existente
- línea 12: el método [destroy] elimina una entidad [Indemnite] existente
- línea 14: el método [find] permite recuperar una entidad [Indemnite] existente mediante su identificador id
- línea 16: el método [findAll] devuelve en una lista todas las entidades [Indemnite] existentes
La interfaz [DAO] para acceder a las entidades [Employe] de la tabla [EMPLOYES] será la siguiente:
- En la línea 6, la interfaz [IEmployeDao] gestiona el acceso a la entidad [Employe] y, por lo tanto, a la tabla [EMPLOYES] de la base de datos. Nuestra aplicación solo necesita el método [findAll] de la línea 16, que permite recuperar todo el contenido de la tabla [EMPLOYES]. Aquí hemos querido plantearnos un caso más general en el que todas las operaciones CRUD (Crear, Leer, Actualizar, Eliminar) se realizan sobre la entidad.
- línea 8: el método [create] crea una nueva entidad [Employe]
- línea 10: el método [edit] modifica una entidad [Employe] existente
- línea 12: el método [destroy] elimina una entidad [Employe] existente
- línea 14: el método [find] permite buscar una entidad [Employe] existente mediante su identificador id
- línea 16: el método [find(String SS)] permite recuperar una entidad [Employe] existente mediante su n.º SS. Hemos visto que este método era necesario para la aplicación de consola.
- línea 18: el método [findAll] devuelve en una lista todas las entidades [Employe] existentes. Ya vimos que este método era necesario para la aplicación gráfica.
5.8. La clase [PamException]
La capa [DAO] trabajará con la clase API de Java. Esta clase API lanza excepciones controladas de tipo [SQLException] que presentan dos inconvenientes:
- aumentan la complejidad del código, que debe manejar obligatoriamente estas excepciones con try / catch.
- deben declararse en la firma de los métodos de la interfaz [IDao] mediante un «throws SQLException». Esto tiene como consecuencia que no se pueda implementar esta interfaz mediante clases que lancen una excepción controlada de un tipo distinto a [SQLException].
Para solucionar este problema, la capa [DAO] solo «elevará» excepciones no controladas de tipo [PamException].
![]() |
- la capa [JDBC] lanza excepciones de tipo [SQLException]
- la capa [JPA] lanza excepciones propias de la implementación JPA utilizada
- la capa [DAO] genera excepciones de tipo [PamException] no controladas
Esto tiene dos consecuencias:
- la capa [metier] no tendrá la obligación de manejar las excepciones de la capa [DAO] con try / catch. Simplemente podrá dejar que se propaguen hasta la capa [ui].
- los métodos de la interfaz [IDao] no tienen que incluir en su firma la naturaleza de la excepción [PamException], lo que deja abierta la posibilidad de implementar esta interfaz con clases que lanzarían otro tipo de excepción no controlada.
La clase [PamException] se colocará en el paquete [exception] del proyecto NetBeans:
![]() |
Su código es el siguiente:
- línea 4: [PamException] deriva de [RuntimeException]. Por lo tanto, se trata de un tipo de excepción que el compilador no nos obliga a manejar mediante un try/catch ni a incluir en la firma de los métodos. Es por esta razón que [PamException] no aparece en la firma de los métodos de la interfaz [IDao]. Esto permite que esta interfaz sea implementada por una clase que lance otro tipo de excepciones, siempre y cuando esta también derive de [RuntimeException].
- Para diferenciar los errores que pueden ocurrir, se utiliza el código de error de la línea 7. Los tres constructores de las líneas 14, 19 y 24 son los de la clase padre [RuntimeException], a los que se les ha agregado un parámetro: el del código de error que se desea asignar a la excepción.
El funcionamiento de la aplicación, desde el punto de vista de las excepciones, será el siguiente:
- la capa [DAO] encapsulará cualquier excepción que se presente en una excepción de tipo [PamException] y la reenviará a la capa [métier].
- La capa [métier] permitirá que se propaguen las excepciones lanzadas por la capa [DAO]. Encapsulará cualquier excepción que surja en la capa [métier] en una excepción de tipo [PamException] y la reenviará a la capa [ui].
- La capa [ui] intercepta todas las excepciones que se transmiten desde las capas [métier] y [DAO]. Se limitará a mostrar la excepción en la consola o en la interfaz gráfica.
Ahora analicemos sucesivamente la implementación de las capas [DAO] y [metier].
5.9. La capa [DAO] de la aplicación [PAM]
Nos situamos en el marco de la siguiente arquitectura:
![]() |
5.9.1. Implementación
Lecturas recomendadas: párrafo 3.1.3 de [ref1]
Pregunta: Utilizando la integración Spring / JPA, escriba las clases [CotisationDao, IndemniteDao, EmployeDao] para implementar las interfaces [ICotisationDao, IIndemniteDao, IEmployeDao]. Cada método de clase interceptará cualquier excepción que se produzca y la encapsulará en una excepción de tipo [PamException] con un código de error específico para la excepción interceptada.
Las clases de implementación formarán parte del paquete [dao]:
![]() |
5.9.2. Configuración
Lecturas recomendadas: párrafo 3.1.5 de [ref1]
La integración DAO / JPA se configura mediante el archivo Spring [spring-config-dao.xml] y los archivos JPA y [persistence.xml]:
![]() |
Pregunta: escribe el contenido de estos dos archivos. Se supone que la base de datos utilizada es la base MySQL5 [dbpam_hibernate] generada por el script SQL [dbpam_hibernate.sql]. El archivo Spring definirá los siguientes tres beans: employeDao de tipo EmployeDao, indemniteDao de tipo IndemniteDao, cotisationDao de tipo CotisationDao. Además, la implementación JPA que se utilizará será Hibernate.
5.9.3. Pruebas
Lecturas recomendadas: párrafos 3.1.6 y 3.1.7 de [ref1]
Ahora que la capa [DAO] está escrita y configurada, podemos probarla. La arquitectura de las pruebas será la siguiente:
![]() |
5.9.4. InitDB
Vamos a crear dos programas de prueba para la capa [DAO]. Estos se colocarán en el paquete [dao] [2] de la rama [Test Packages] [1] del proyecto NetBeans. Esta rama no se incluye en el proyecto generado por la opción [Build project], lo que nos asegura que los programas de prueba que coloquemos en ella no se incluirán en el archivo .jar final del proyecto.
![]() |
Las clases colocadas en la rama [Test Packages] tienen acceso a las clases presentes en la rama [Source Packages], así como a las bibliotecas de clases del proyecto. Si las pruebas necesitan bibliotecas distintas a las del proyecto, estas deben declararse en la rama [Test Libraries] [2].
Las clases de prueba utilizan la herramienta de pruebas unitarias JUnit:
- [JUnitInitDB] no realiza ninguna prueba. Llena la base de datos con algunos registros y luego los muestra en la consola.
- [JUnitDao] realiza una serie de pruebas y verifica los resultados.
El esqueleto de la clase [JUnitInitDB] es el siguiente:
- El método [init] se ejecuta antes de que comience la serie de pruebas (anotación @BeforeClass). Instancia la capa [DAO].
- El método [clean] se ejecuta antes de cada prueba (anotación @Before). Vacía la base de datos.
- El método [initDB] es una prueba (anotación @Test). Es la única. Una prueba debe contener instrucciones de afirmación Assert.assertCondition. Aquí no habrá ninguna. Por lo tanto, el método es una prueba falsa. Su función es llenar la base de datos con algunas filas y luego mostrar el contenido de la base en la consola. Aquí se utilizan los métodos create y findAll de las capas [DAO].
Pregunta: complete el código de la clase [JUnitInitDB]. Utilice como referencia el ejemplo del párrafo 3.1.6 de [ref1]. El código generará el contenido presentado en el párrafo 5.1.
5.9.5. Implementació ación de las pruebas
Ahora estamos listos para ejecutar [InitDB]. Describimos el procedimiento con los programas SGBD y MySQL5:
![]() |
- se han implementado las clases [1], los archivos de configuración [2] y las clases de prueba de la capa [DAO] y [3],
![]() |
- se compila el proyecto [4]
- se ejecuta la clase [JUnitInitDB] [5]. Se inicia SGBD MySQL5 con una base existente [dbpam_hibernate],
- la ventana de [Test Results] y [6] indica que las pruebas se han realizado con éxito. Este mensaje no es relevante en este caso, ya que el programa [JUnitInitDB] no contiene ninguna instrucción de afirmación Assert.assertCondition que pudiera provocar el fracaso de la prueba. No obstante, esto demuestra que no se produjo ninguna excepción durante la ejecución de la prueba.
La ventana [Output] contiene los registros de la ejecución, los de Spring y los de la prueba en sí. Las salidas generadas por la clase [JUnitInitDB] son las siguientes:
Las tablas [EMPLOYES, INDEMNITES, COTISATIONS] se han llenado. Esto se puede verificar con una conexión de NetBeans a la base de datos [dbpam_hibernate].
![]() |
- en [1], en la pestaña [services], se visualizan los datos de la tabla [employes] de la conexión [dbpam_hibernate] [2],
- en [3] se muestra el resultado.
5.9.6. JUnitDao
Ahora nos enfocamos en una segunda clase de pruebas, [JUnitDao]:
![]() |
El esqueleto de la clase será el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | |
En la clase de pruebas anterior, la base de datos se vacía antes de cada prueba.
Pregunta: escribe los siguientes métodos:
1 - test02: nos basaremos en test01
2 - test03: un empleado tiene un campo de tipo Indemnite. Por lo tanto, hay que crear una entidad Indemnite y una entidad Employe
3 - test04.
Al proceder de la misma manera que para la clase de pruebas [JUnitInitDB], se obtienen los siguientes resultados:
![]() |
- en [1], se ejecuta la clase de pruebas
- en [2], los resultados de las pruebas en la ventana [Test Results]
Provocamos un error para ver cómo se reporta en la página de resultados:
Línea 13: la afirmación provocará un error, ya que el valor de Csgrds es 3,49 (línea 8). La ejecución de la clase de pruebas arroja los siguientes resultados:
![]() |
- La página de resultados [1] ahora muestra que hubo pruebas fallidas.
- En [2], se muestra un resumen de la excepción que provocó el fallo de la prueba. Allí se encuentra el número de línea del código Java donde ocurrió la excepción.
5.10. La capa [metier] de la aplicación [PAM]
Ahora que ya se ha escrito la capa [DAO], pasamos al análisis de la capa de negocio [2]:
![]() |
5.10.1. La interfaz Java [IMetier]
Esta se describió en el párrafo 5.7. A continuación la recordamos:
La implementación de la capa [metier] se realizará en un paquete [metier]:
![]() |
El paquete [metier] incluirá, además de la interfaz [IMetier] y su implementación [Metier], otras dos clases: [FeuilleSalaire] y [ElementsSalaire]. La clase [FeuilleSalaire] se presentó brevemente en el párrafo 5.7. Ahora volveremos a ella.
5.10.2. La clase [FeuilleSalaire]
El método [calculerFeuilleSalaire] de la interfaz [IMetier] devuelve un objeto de tipo [FeuilleSalaire] que representa los distintos elementos de una nómina. Su definición es la siguiente:
- línea 7: la clase implementa la interfaz Serializable porque sus instancias pueden intercambiarse a través de la red.
- línea 9: el empleado al que se refiere la nómina
- línea 10: las diferentes tasas de cotización
- línea 11: las diferentes indemnizaciones relacionadas con el índice del empleado
- línea 12: los componentes de su salario
- líneas 14-22: los dos constructores de la clase
- líneas 25-27: método [toString] que identifica un objeto [FeuilleSalaire] específico
- líneas 29 y siguientes: los accesores públicos a los campos privados de la clase
La clase [ElementsSalaire], a la que se hace referencia en la línea 11 de la clase [FeuilleSalaire] anterior, reúne los elementos que conforman una nómina. Su definición es la siguiente:
- línea 3: la clase implementa la interfaz Serializable porque es un componente de la clase FeuilleSalaire, la cual debe ser serializable.
- línea 6: el salario base
- línea 7: las cotizaciones sociales pagadas sobre este salario base
- línea 8: las indemnizaciones diarias por manutención del niño
- línea 9: las indemnizaciones diarias por comida del niño
- línea 10: el salario neto a pagar a la niñera
- líneas 12-24: los constructores de la clase
- líneas 27-31: método [toString] que identifica un objeto [ElementsSalaire] específico
- líneas 34 y siguientes: los accesores públicos a los campos privados de la clase
5.10.3. La clase de implementación [Metier] de la capa [metier]
La clase de implementación [Metier] de la capa [metier] podría ser la siguiente:
- línea 5: la anotación de Spring @Transactional hace que cada método de la clase se ejecute dentro de una transacción.
- líneas 9-10: las referencias en las capas [DAO] a las entidades [Cotisation, Employe, Indemnite]
- líneas 14-17: el método [calculerFeuilleSalaire]
- líneas 20-22: el método [findAllEmployes]
- línea 24 y siguientes: los accesores públicos de los campos privados de la clase
Pregunta: escribe el código del método [findAllEmployes].
Pregunta: escribe el código del método [calculerFeuilleSalaire].
Cabe destacar lo siguiente:
- el modo de cálculo del salario se explicó en el párrafo 5.2.
- Si el parámetro [SS] no corresponde a ningún empleado (la capa [DAO] devolvió un puntero null), el método lanzará una excepción de tipo [PamException] con un código de error apropiado.
5.10.4. Pruebas de la capa [metier]
Creamos dos programas de prueba:
![]() |
Las clases de prueba [3] se crean en un paquete [metier] [2] de la rama [Test Packages] [1] del proyecto.
La clase [JUnitMetier_1] podría ser la siguiente:
No hay ninguna afirmación Assert.assertCondition en la clase. Simplemente se busca calcular algunos salarios para luego verificarlos manualmente. La salida en pantalla obtenida al ejecutar la clase anterior es la siguiente:
- línea 4: la nómina de Justine Laverti
- línea 5: la nómina de Marie Jouveinal
- línea 6: la excepción debida a que el empleado con n.º SS «xx» no existe.
Pregunta: la línea 17 de [JUnitMetier_1] utiliza el bean de Spring denominado metier. Indique la definición de este bean en el archivo [spring-config-metier-dao.xml].
La clase [JUnitMetier_2] podría ser la siguiente:
La clase [JUnitMetier_2] es una copia de la clase [JUnitMetier_1], en la que, en esta ocasión, se han colocado aserciones en el método test01.
Pregunta: escribe el método test01.
Al ejecutar la clase [JUnitMetier_2], se obtienen los siguientes resultados si todo sale bien:

5.11. La capa [ui] de la aplicación [PAM] – versión console
Ahora que ya se ha escrito la capa [metier], nos queda escribir la capa [ui] [1]:
![]() |
Crearemos dos implementaciones diferentes de la capa [ui]: una versión console y una versión gráfica swing:
![]() |
5.11.1. La clase [ui.console.Main]
En primer lugar, nos enfocamos en la aplicación de consola implementada por la clase [ui.console.Main] mencionada anteriormente. Su funcionamiento se describió en el párrafo 5.3. El esqueleto de la clase [Main] podría ser el siguiente:
Pregunta: complete el código anterior.
5.11.2. Ejecución
Para ejecutar la clase [ui.console.Main], se procederá de la siguiente manera:
![]() |
- en [1], seleccione las propiedades del proyecto,
- en [2], seleccionar la propiedad [Run] del proyecto,
- utilizar el botón [3] para designar la clase (denominada clase principal) que se ejecutará,
- seleccionar la clase [4],
- la clase aparece en [5]. Esta requiere tres argumentos para ejecutarse (n.º SS, número de horas trabajadas, número de días trabajados). Estos argumentos se colocan en [6],
- una vez hecho esto, se puede ejecutar el proyecto [7]. La configuración anterior hace que sea la clase [ui.console.Main] la que se ejecute.
Los resultados de la ejecución se obtienen en la ventana [output]:
![]() | ![]() |
5.12. La capa [ui] de la aplicación [PAM] – versión gráfica
Ahora implementamos la capa [ui] con una interfaz gráfica:
![]() |
![]() |
- en [1], la clase [PamJFrame] de la interfaz gráfica
- en [2]: la interfaz gráfica
5.12.1. Un tutorial rápido
Para crear la interfaz gráfica, se puede proceder de la siguiente manera:
![]() |
- [1]: se crea un nuevo archivo con el botón [1] [New File...]
- [2]: se elige la categoría del archivo [Swing GUI Forms], c.a.d. formularios gráficos
- [3]: se elige el tipo [JFrame Form], un tipo de formulario vacío
![]() |
- [5]: se le da un nombre al formulario, que también será una clase
- [6]: se coloca el formulario en un paquete
- [8]: el formulario se agrega al árbol del proyecto
- [9]: se puede acceder al formulario desde dos perspectivas: [Design] y [9], que permiten diseñar los distintos componentes del formulario; y [Source] y [10 ci-dessous], que dan acceso al código Java del formulario. En definitiva, un formulario es una clase de Java como cualquier otra. La perspectiva [Design] facilita el diseño del formulario. Cada vez que se agrega un componente en el modo [Design], se agrega código Java en la perspectiva [Source] para incorporarlo.
![]() |
- [11]: la lista de componentes Swing disponibles para un formulario se encuentra en la ventana [Palette].
- [12]: la ventana [Inspector] muestra el árbol de componentes del formulario. Los componentes con representación visual se encuentran en la rama [JFrame], y los demás, en la rama [Other Components].
![]() |
- en [13], seleccionamos un componente [JLabel] con un simple clic
- en [14], lo colocamos en el formulario en modo [Design]
- en [15], definimos las propiedades del JLabel (texto, fuente).
![]() |
- en [16], el resultado obtenido.
- En [17], solicitamos la vista previa del formulario
- en [18], el resultado
- en [19], la etiqueta [JLabel1] se ha agregado al árbol de componentes en la ventana [Inspector]
![]() |
- en [20] y [21]: en la perspectiva [Source] del formulario, se ha agregado código Java para gestionar el JLabel agregado.
Hay un tutorial sobre la creación de formularios con NetBeans disponible en la URL [http://www.netbeans.org/kb/trails/matisse.html].
5.12.2. La interfaz gráfica [PamJFrame]
Crearemos la siguiente interfaz gráfica:
![]() |
- en [1], la interfaz gráfica
- en [2], el árbol de sus componentes: un JLabel y seis contenedores JPanel
JLabel1
![]() |
JPanel1
![]() | ![]() |
JPanel2
![]() | ![]() |
JPanel3
![]() | ![]() |
JPanel4
![]() | ![]() |
JPanel5
![]() | ![]() |
Ejercicio práctico: construye la interfaz gráfica anterior con la ayuda del tutorial [http://www.netbeans.org/kb/trails/matisse.html].
5.12.3. Los eventos de la interfaz gráfica
Lecturas recomendadas: capítulo [Interfaces graphiques] de [ref2].
Gestionaremos el clic en el botón [jButtonSalaire]. Para crear el método que maneje este evento, podemos proceder de la siguiente manera:
![]() |
Se genera el controlador del clic en el botón [JButtonSalaire]:
También se genera el código Java que asocia el método anterior al clic en el botón [JButtonSalaire]:
Las líneas 2 a 5 indican que el clic (evento de tipo ActionPerformed) en el botón [jButtonSalaire] (línea 2) debe ser manejado por el método [jButtonSalaireActionPerformed] (línea 4).
También manejaremos el evento [caretUpdate] (desplazamiento del cursor de entrada) en el campo de entrada [jTextFieldHT]. Para crear el manejador de este evento, procedemos como anteriormente:
![]() |
Se genera el controlador del evento [caretUpdate] en el campo de entrada [jTextFieldHT]:
También se genera el código Java que asocia el método anterior al evento [caretUpdate] en el campo de entrada [jTextFieldHT]:
Las líneas 1 a 4 indican que el evento [caretUpdate] (línea 2) en el botón [jTextFieldHT] (línea 1) debe ser manejado por el método [ jTextFieldHTCaretUpdate] (línea 3).
5.12.4. Inicialización de la interfaz gráfica
Volvamos a la arquitectura de nuestra aplicación:
![]() |
La capa [ui] necesita una referencia a la capa [metier]. Recordemos cómo se obtuvo esta referencia en la aplicación console:
El método es el mismo en la aplicación gráfica. Es necesario que, al inicializarse esta, también se inicialice la referencia [IMetier metier] de la línea 3 anterior. El código generado para la interfaz gráfica es, por el momento, el siguiente:
- líneas 29-35: el método estático [main] que inicia la aplicación
- línea 32: se crea una instancia de la interfaz gráfica [PamJFrame] y se hace visible.
- líneas 7-9: el constructor de la interfaz gráfica.
- línea 8: llamada al método [initComponents] definido en la línea 17. Este método se genera automáticamente a partir del trabajo realizado en el modo [Design]. No se debe modificar.
- línea 21: el método que gestionará el desplazamiento del cursor de entrada en el campo [jTextFieldHT]
- línea 25: el método que gestionará el clic en el botón [jButtonSalaire]
Para agregar nuestras propias inicializaciones al código anterior, podemos proceder de la siguiente manera:
- línea 4: se llama a un método propio para realizar nuestras propias inicializaciones. Estas se definen en el código de las líneas 10 a 42
Pregunta: con la ayuda de los comentarios, completa el código del procedimiento [doMyInit].
5.12.5. Manejadores de eventos
Pregunta: Escribe el método [jTextFieldHTCaretUpdate]. Este método debe garantizar que, si el dato presente en el campo [jTextFieldHT] no es un número real >=0, el botón [jButtonSalaire] quede inactivo.
Pregunta: Escriba el método [jButtonSalaireActionPerformed], que debe mostrar la nómina del empleado seleccionado en [jComboBoxEmployes].
5.12.6. Ejecución de la interfaz gráfica
Para ejecutar la interfaz gráfica, modificaremos la configuración [Run] del proyecto:
![]() |
- a [1], y se debe establecer la clase de la interfaz gráfica
El proyecto debe estar completo con sus archivos de configuración (persistence.xml, spring-config-metier-dao.xml) y la clase de la interfaz gráfica. Se lanzará el objetivo SGBD antes de ejecutar el proyecto.
5.13. Implementación de la capa JPA con EclipseLink
Nos interesa la siguiente arquitectura, en la que la capa JPA ahora está implementada por EclipseLink:
![]() |
5.13.1. El proyecto de NetBeans
El nuevo proyecto de NetBeans se obtiene copiando el proyecto anterior:
![]() |
- en [1]: después de hacer clic con el botón derecho del ratón en el proyecto Hibernate, seleccione Copy
- con el botón [2], elija la carpeta principal del nuevo proyecto. El nombre de la carpeta aparece en [3].
- En [4], asigne un nombre al nuevo proyecto
- en [5], el nombre de la carpeta del proyecto
![]() |
- en [1], se ha creado el nuevo proyecto. Tiene el mismo nombre que el original,
- en [2] y [3], lo renombramos como [mv-pam-spring-eclipselink].
El proyecto debe modificarse en dos puntos para adaptarlo a la nueva capa JPA / EclipseLink:
- En [4], se deben modificar los archivos de configuración de Spring. En ellos se encuentra, de hecho, la configuración de la capa JPA.
- En [5], hay que modificar las bibliotecas del proyecto: las de Hibernate deben sustituirse por las de EclipseLink.
Comencemos por este último punto. El archivo [pom.xml] para el nuevo proyecto será este:
<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-pam-spring-eclipselink</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-spring-eclipselink</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>swing-layout</id>
<layout>default</layout>
<name>Repository for library Library[swing-layout]</name>
</repository>
<repository>
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
<id>eclipselink</id>
<layout>default</layout>
<name>Repository for library Library[eclipselink]</name>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
</project>
- líneas 73-82: las dependencias para la implementación JPA EclipseLink,
- líneas 19-24: el repositorio de Maven para EclipseLink.
Los archivos de configuración de Spring deben modificarse para indicar que la implementación JPA ha cambiado. En ambos archivos, solo cambia la sección que configura la capa JPA. Por ejemplo, en [spring-config-metier-dao.xml] tenemos:
Las líneas 19-36 configuran la capa JPA. La implementación JPA utilizada es Hibernate (línea 22). Además, la base de datos de destino es [dbpam_hibernate] (línea 41).
Para cambiar a una implementación JPA / EclipseLink, las líneas 19 a 35 anteriores se reemplazan por las siguientes:
- línea 5: la implementación JPA utilizada es EclipseLink
- línea 9: la propiedad databasePlatform establece el SGBD de destino, en este caso MySQL
- línea 11: para generar las tablas de la base de datos cuando se instancia la capa JPA. Aquí, la propiedad está comentada.
- línea 7: para visualizar en la consola los comandos SQL emitidos por la capa JPA. Aquí, la propiedad está comentada.
Además, la base de datos de destino pasa a ser [dbpam_eclipselink] (línea 4 a continuación):
5.13.2. Ejecución de las pruebas
Antes de probar la aplicación completa, es recomendable verificar si las pruebas JUnit se ejecutan correctamente con la nueva implementación JPA. Antes de realizarlas, comenzaremos por eliminar las tablas de la base de datos. Para ello, en la pestaña [Runtime] de NetBeans, si es necesario, crearemos una conexión a la base de datos dbpam_eclipselink / MySQL5. Una vez conectado a la base de datos dbpam_eclipselink / MySQL5, podremos proceder a eliminar las tablas como se muestra a continuación:
- [1]: antes de la eliminación
- [2]: después de la eliminación
![]() |
Una vez hecho esto, se puede ejecutar la primera prueba en la capa [DAO]: InitDB, que llena la base de datos. Para que la aplicación vuelva a crear las tablas eliminadas anteriormente, hay que asegurarse de que en la configuración de Spring para JPA / EclipseLink la línea:
exista y no esté comentada.
Compilamos el proyecto (Build) y luego ejecutamos la prueba [JUnitInitDB] :
![]() |
- en [1], se ejecuta la prueba InitDB.
- En [2], falla. La excepción la genera Spring y no una prueba que haya fallado.
Causado por: org.springframework.beans.factory.BeanCreationException: Error al crear el bean con el nombre 'entityManagerFactory' definido en el recurso de ruta de clases [spring-config-DAO.xml]: Falló la invocación del método init; la excepción anidada es java.lang.IllegalStateException: Se debe iniciar el agente de Java para utilizar InstrumentationLoadTimeWeaver. Consulte la documentación de Spring.
Spring indica que hay un problema de configuración. El mensaje no es claro. La razón de la excepción se explicó en el párrafo 3.1.9 de [ref1]. Para que la configuración de Spring / EclipseLink funcione, el JVM que ejecuta la aplicación debe iniciarse con un parámetro específico: un agente Java. La forma de este parámetro es la siguiente:
[spring-agent.jar] es el agente Java que necesita JVM para gestionar la configuración de Spring / EclipseLink.
Al ejecutar un proyecto, es posible pasar argumentos a JVM:
![]() |
- en [1], se accede a las propiedades del proyecto
- en [2], se acceden las propiedades de Run
- en [3], se pasa el parámetro -javaagent a JVM
5.13.3. InitDB
Ahora estamos listos para volver a probar [InitDB]. Esta vez, los resultados obtenidos son los siguientes:
![]() |
- en [1], la prueba se realizó con éxito
- en [2], en la pestaña [Services], se actualiza la conexión que tiene NetBeans con la base de datos [dbpam_eclipselink]
- en [3], se crearon cuatro tablas
![]() |
- en [5], se visualiza el contenido de la tabla [employes]
- en [6], el resultado.
5.13.4. JUnitDao
La ejecución de la clase de pruebas [JUnitDao] puede fallar, aunque con la implementación JPA / Hibernate sí había tenido éxito. Para entender por qué, analicemos un ejemplo.
El método que se prueba es el siguiente: IndemniteDao.create:
- líneas 15-22: el método que se prueba
El método de prueba es el siguiente:
package dao;
...
public class JUnitDao {
// capas DAO
static private IEmployeDao employeDao;
static private IIndemniteDao indemniteDao;
static private ICotisationDao cotisationDao;
@BeforeClass
public static void init() {
// registro
log("init");
// configuración de la aplicación
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-DAO.xml");
// capas DAO
employeDao = (IEmployeDao) ctx.getBean("employeDao");
indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
}
@Before()
public void clean() {
// se vacía la base de datos
for (Employe employe : employeDao.findAll()) {
employeDao.destroy(employe);
}
for (Cotisation cotisation : cotisationDao.findAll()) {
cotisationDao.destroy(cotisation);
}
for (Indemnite indemnite : indemniteDao.findAll()) {
indemniteDao.destroy(indemnite);
}
}
// registros
private static void log(String message) {
System.out.println("----------- " + message);
}
// pruebas
….
@Test
public void test05() {
log("test05");
// se crean dos indemnizaciones con el mismo índice
// viola la restricción de unicidad del índice
boolean erreur = true;
Indemnite indemnite1 = null;
Indemnite indemnite2 = null;
Throwable th = null;
try {
indemnite1 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
indemnite2 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
erreur = false;
} catch (PamException ex) {
th = ex;
// verificaciones
Assert.assertEquals(31, ex.getCode());
} catch (Throwable th1) {
th = th1;
}
// verificaciones
Assert.assertTrue(erreur);
// cadena de excepciones
System.out.println("Chaîne des exceptions --------------------------------------");
System.out.println(th.getClass().getName());
while (th.getCause() != null) {
th = th.getCause();
System.out.println(th.getClass().getName());
}
// la primera indemnización debió haberse guardado
Indemnite indemnite = indemniteDao.find(indemnite1.getId());
// verificación
Assert.assertNotNull(indemnite);
Assert.assertEquals(1, indemnite.getIndice());
Assert.assertEquals(1.93, indemnite.getBaseHeure(), 1e-6);
Assert.assertEquals(2, indemnite.getEntretienJour(), 1e-6);
Assert.assertEquals(3, indemnite.getRepasJour(), 1e-6);
Assert.assertEquals(12, indemnite.getIndemnitesCP(), 1e-6);
// la segunda indemnización no debió haberse guardado
List<Indemnite> indemnites = indemniteDao.findAll();
int nbIndemnites = indemnites.size();
Assert.assertEquals(nbIndemnites, 1);
}
...
}
Pregunta: explique qué hace la prueba test05 e indique los resultados esperados.
Los resultados obtenidos con una capa JPA / Hibernate son los siguientes:
La prueba pasa, c.a.d. Las aserciones se verifican y no hay ninguna excepción que salga del método de prueba.
Pregunta: explique qué sucedió.
Los resultados obtenidos con una capa JPA / EclipseLink son los siguientes:
Al igual que anteriormente con Hibernate, la prueba pasa, c.a.d. Las aserciones se verifican y no hay ninguna excepción que salga del método de prueba.
Pregunta: explique qué sucedió.
Pregunta: a partir de estos dos ejemplos, ¿qué se puede concluir sobre la intercambiabilidad de las implementaciones JPA? ¿Es total en este caso?
5.13.5. Las demás pruebas
Una vez que la capa [DAO] haya sido probada y se considere correcta, se podrá pasar a las pruebas de la capa [metier] y a las del proyecto mismo, tanto en su versión de consola como en la gráfica. El cambio en la implementación JPA no afecta en absoluto a las capas [metier] y [ui]; por lo tanto, si estas capas funcionaban con Hibernate, funcionarán con EclipseLink con algunas excepciones: el ejemplo anterior muestra, de hecho, que las excepciones lanzadas por las capas [DAO] pueden diferir. Así, en el caso de la prueba, Spring / JPA / Hibernate lanza una excepción de tipo [PamException], una excepción propia de la aplicación [pam], mientras que Spring / JPA / EclipseLink, por su parte, lanza una excepción de tipo [TransactionSystemException], una excepción del marco Spring. Si en el caso de uso de la prueba, la capa [ui] espera una excepción de tipo [PamException] porque se ha creado con Hibernate, dejará de funcionar cuando se cambie a EclipseLink.
5.13.6. Tarea a realizar
Tarea práctica: volver a realizar las pruebas de las aplicaciones console y swing con diferentes SGBD: MySQL5, Oracle XE, SQL Server.





















































































