6. Versión 2: Arquitectura OpenEJB / JPA
6.1. Introducción a los principios de la migración
Aquí presentamos los principios que regirán la migración de una aplicación JPA / Spring / Hibernate a una aplicación JPA / OpenEJB / EclipseLink. Esperaremos hasta el párrafo 6.2 para crear los proyectos de Maven.
6.1.1. Las dos arquitecturas
La implementación actual con Spring / Hibernate
![]() |
La implementación que se va a construir con OpenEJB / EclipseLink
![]() |
6.1.2. Las bibliotecas de los proyectos
- Las capas [DAO] y [metier] ya no son instanciadas por Spring. Son instanciadas por el contenedor OpenEJB.
- Las bibliotecas del contenedor Spring y su configuración desaparecen y son reemplazadas por las bibliotecas del contenedor OpenEJB y su configuración.
- Las bibliotecas de la capa JPA / Hibernate se reemplazan por las de la capa JPA / EclipseLink
6.1.3. Configuración de la capa JPA / EclipseLink / OpenEJB
- El archivo [META-INF/persistence.xml], que configura la capa JPA, queda así:
- línea 3: las transacciones en un contenedor EJB son de tipo JTA (transacción Java API). Con Spring, eran de tipo RESOURCE_LOCAL.
- línea 9: la implementación JPA utilizada es EclipseLink
- líneas 5-7: las entidades gestionadas por la capa JPA
- líneas 11-13: propiedades del proveedor EclipseLink
- línea 12: en cada ejecución, se crearán las tablas
Las características JDBC de la fuente de datos JTA utilizada por el contenedor OpenEJB se especificarán mediante el siguiente archivo de configuración [conf/openejb.conf]:
- línea 3: se utiliza el identificador «Default JDBC Database» cuando se trabaja con un contenedor OpenEJB integrado (embedded) en la propia aplicación.
- línea 5: utilizamos una base de datos MySQL [dbpam_eclipselink]
6.1.4. Implementación de la capa [DAO] mediante EJB
- Las clases que implementan la capa [DAO] se convierten en EJB. Tomemos como ejemplo la clase [CotisationDao]:
La interfaz [ICotisationDao] en la versión de Spring era la siguiente:
La clase EJB implementará esta misma interfaz de dos formas diferentes: una local y otra remota. La interfaz local puede ser utilizada por un cliente que se ejecute en el mismo JVM, mientras que la interfaz remota puede ser utilizada por un cliente que se ejecute en otro JVM.
La interfaz local:
- línea 6: la interfaz [ICotisationDaoLocal] hereda de la interfaz [ICotisationDao] para adoptar todos sus métodos. No agrega ninguno nuevo.
- línea 5: la anotación @Local la convierte en una interfaz local para la interfaz EJB, que la implementará.
La interfaz remota:
- línea 6: la interfaz [ICotisationDaoRemote] hereda de la interfaz [ICotisationDao] para adoptar todos sus métodos. No agrega ninguno nuevo.
- línea 5: la anotación @Remote la convierte en una interfaz remota para EJB, que la implementará.
La capa [DAO] es implementada por un EJB que implementa ambas interfaces (esto no es obligatorio):
- línea 1: la anotación @Stateless, que convierte a la clase en un EJB
- línea 2: la anotación @TransactionAttribute, que hace que cada método de la clase se ejecute dentro de una transacción.
- línea 5: la anotación @PersistenceContext, que inyecta en la clase [CotisationDao] el EntityManager de la capa JPA. Es idéntica a la que teníamos en la versión de Spring.
Cuando se utiliza la interfaz local de la capa [DAO], el cliente de esta interfaz se ejecuta en la misma JVM.
![]() |
En el ejemplo anterior, las capas [metier] y [DAO] intercambian objetos por referencia. Cuando una capa modifica el objeto compartido, la otra capa detecta ese cambio.
Cuando se utiliza la interfaz remota de la capa [DAO], el cliente de dicha interfaz suele ejecutarse en otra JVM.
![]() |
En el ejemplo anterior, las capas [metier] y [DAO] intercambian objetos por valor (serialización del objeto intercambiado). Cuando una capa modifica un objeto compartido, la otra capa solo detecta ese cambio si se le devuelve el objeto modificado.
6.1.5. Implementación de la capa [metier] mediante una EJB
- La clase que implementa la capa [metier] también se convierte en un EJB que implementa una interfaz local y remota. La interfaz inicial [IMetier] era la siguiente:
Se crean una interfaz local y una interfaz remota a partir de la interfaz anterior:
La clase EJB de la capa [metier] implementa estas dos interfaces:
- líneas 1-2: definen un EJB en el que cada método se ejecuta en una transacción.
- línea 7: una referencia a la interfaz local de EJB [CotisationDao].
- línea 6: la anotación @EJB indica que el contenedor EJB debe inyectar una referencia a la interfaz local de EJB [CotisationDao].
- Líneas 8-11: se repite el mismo proceso para las interfaces locales de EJB, [EmployeDao] y [IndemniteDao].
Al final, cuando se instancien los EJB y [Metier], los campos de las líneas 7, 9 y 11 se inicializarán con referencias a las interfaces locales de los tres EJB de la capa [DAO]. Por lo tanto, aquí se supone que las capas [metier] y [DAO] se ejecutarán en la misma JVM.
![]() |
6.1.6. Los clientes de EJB
![]() |
En el diagrama anterior, para comunicarse con la capa [metier], la capa [ui] debe obtener una referencia a la interfaz remota de la capa EJB de la capa [metier].
![]() |
En el esquema anterior, para comunicarse con la capa [metier], la capa [ui] debe obtener una referencia a la interfaz local de la capa EJB de la capa [metier]. El método para obtener estas referencias varía de un contenedor a otro. Para el contenedor OpenEJB, se puede proceder de la siguiente manera:
Referencia en la interfaz local:
- líneas 2-5: se inicializa el contenedor OpenEJB.
- línea 5: se cuenta con un contexto JNDI (Java Naming and Directory Interface) que permite obtener referencias sobre los EJB. Cada EJB se identifica con un nombre JNDI:
- (continuación)
- para la interfaz local, se agrega «Local» al nombre del EJB (líneas 7-9)
- para la interfaz remota, se agrega «Remote» al nombre del EJB
Con Java EE 5, estas reglas varían según el contenedor EJB. Esto representa una dificultad. Java EE 6 introdujo una notación JNDI compatible con todos los servidores de aplicaciones.
El código anterior obtiene referencias a las interfaces locales de EJB a través de sus nombres JNDI. Anteriormente mencionamos que estas también se pueden obtener mediante la anotación @EJB. Por lo tanto, tal vez se desee escribir:
La anotación @EJB solo se respeta si pertenece a una clase cargada por el contenedor EJB. Este será el caso, por ejemplo, de la clase [Metier]. El código anterior, por su parte, pertenecerá a una clase de consola que no será cargada por el contenedor EJB. Por lo tanto, nos vemos obligados a utilizar los nombres JNDI o EJB.
A continuación, el código para obtener una referencia a la interfaz remota de EJB y [Metier]:
6.2. Trabajo práctico
Nos proponemos migrar la aplicación NetBeans Spring / Hibernate a una arquitectura OpenEJB / EclipseLink.
La implementación actual con Spring / Hibernate
![]() |
La implementación que se va a desarrollar con OpenEJB / EclipseLink
![]() |
6.2.1. Configuración de la base de datos [dbpam_eclipselink]
Si no existe, cree la base de datos MySQL [dbpam_eclipselink]. Si existe, elimine todas sus tablas. Cree una conexión de NetBeans a esta base de datos tal como se describe en el párrafo 6.2.1.
6.2.2. Configuración inicial del proyecto de NetBeans
- Cargar el proyecto Maven [mv-pam-spring-hibernate]
- Crea un nuevo proyecto Maven Java [mv-pam-openejb-eclipselink] [1]
![]() |
- En la pestaña [Files] [2], crea una carpeta [conf] [3] en la raíz del proyecto
- coloca en esta carpeta el siguiente archivo [openejb.conf] [4]:
![]() |
- crea la carpeta [src / main/ resources/ META-INF] [5]
- colocar en ella el archivo [persistence.xml] [6] siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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_1_0.xsd">
<persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
<!-- el proveedor JPA es EclipseLink -->
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- entidades Jpa -->
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<!-- propiedades del proveedor EclipseLink -->
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
- línea 12: se solicitan registros detallados a EclipseLink,
- línea 13: las tablas se crearán al instanciar la capa JPA,
- Agrega las bibliotecas OpenEJB, EclipseLink y el controlador JDBC de MySQL al archivo [pom.xml] del proyecto:
<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-openejb-eclipselink</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-openejb-eclipselink</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.openejb</groupId>
<artifactId>openejb-core</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<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>
<repositories>
<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>
</project>
- líneas 18-22: la dependencia OpenEJB,
- líneas 30-39: las dependencias EclipseLink,
- líneas 41-44: la dependencia del controlador JDBC de MySQL
6.2.3. Portación de la capa [DAO]
Vamos a realizar la migración de la capa [DAO] copiando los paquetes del proyecto [mv-pam-spring-hibernate] al proyecto [mv-pam-openejb-eclipselink].
- Copiar los paquetes [dao, exception, jpa]
![]() |
Los errores señalados anteriormente se deben a que la capa [DAO] copiada utiliza Spring y las bibliotecas de Spring ya no forman parte del proyecto.
6.2.3.1. El EJB [CotisationDao]
Creamos las interfaces local y remota del futuro EJB [CotisationDao]:
La interfaz local ICotisationDaoLocal:
Para obtener los paquetes correctos import, ejecute [clic droit sur le code / Fix Imports].
La interfaz remota ICotisationDaoRemote:
Luego modificamos la clase [CotisationDao] para convertirla en una EJB:
El import que generaba esta clase en el marco Spring desaparece. Generar un [Clean and Build] del proyecto:
![]() |
En el [1], ya no hay errores en la clase [CotisationDao].
6.2.3.2. Los archivos EJB, [EmployeDao] y [IndemniteDao]
Se repite el mismo procedimiento para los demás elementos de la capa [DAO]:
- las interfaces IEmployeDaoLocal y IEmployeDaoRemote, derivadas de IEmployeDao
- EJB y EmployeDao, que implementan estas dos interfaces
- interfaces IIndemniteDaoLocal y IIndemniteDaoRemote, derivadas de IIndemniteDao
- EJB y IndemniteDao, que implementan estas dos interfaces
Una vez hecho esto, ya no hay errores en el proyecto [2].
6.2.3.3. La clase [PamException]
La clase [PamException] sigue siendo la misma, salvo por un detalle:
Se ha agregado la línea 5. Para obtener los import correctos, cambie a [Fix imports].
Para entender la anotación de la línea 5, hay que recordar que cada método de EJB de nuestra capa [DAO]:
- se ejecuta en una transacción iniciada y finalizada por el contenedor EJB
- lanza una excepción de tipo [PamException] tan pronto como algo sale mal
![]() |
Cuando la capa [metier] llama a un método M de la capa [DAO], esta llamada es interceptada por el contenedor EJB. Todo ocurre como si hubiera una clase intermedia entre la capa [metier] y la capa [DAO], aquí denominada [Proxy EJB], que intercepta todas las llamadas a la capa [DAO]. Cuando se intercepta la llamada al método M de la capa [DAO], el proxy EJB inicia una transacción y luego le cede el control al método M de la capa [DAO], el cual se ejecuta entonces dentro de esa transacción. El método M finaliza con o sin excepción.
- Si el método M finaliza sin excepción, la ejecución vuelve al proxy EJB, el cual finaliza la transacción validándola mediante un commit. A continuación, el flujo de ejecución se devuelve al método llamante de la capa [metier]
- Si el método M termina con una excepción, la ejecución vuelve al proxy EJB, el cual finaliza la transacción invalidándola mediante un rollback. Además, encapsula esta excepción en un tipo EJBException. A continuación, el flujo de ejecución se devuelve al método que lo invocó en la capa [metier], el cual recibe, por lo tanto, un EJBException. La anotación de la línea 5 anterior impide esta encapsulación. Por lo tanto, la capa [metier] recibirá un PamException. Además, el atributo rollback=true le indica al proxy EJB que, cuando reciba un PamException, debe invalidar la transacción.
6.2.3.4. Prueba de la capa [DAO]
Nuestra capa [DAO], implementada por EJB, puede someterse a pruebas. Comenzamos copiando el paquete [dao] de [Test Packages] del proyecto [mv-pam-springhibernate] al proyecto en desarrollo [1]:
![]() |
Solo conservamos la prueba [JUnitInitDB], que inicializa la base de datos con algunos datos de [2]. Renombramos la clase [ JUnitInitDbLocal] como [3]. La clase [JUnitInitDBLocal] utilizará la interfaz local de EJB de la capa [DAO].
En primer lugar, modificamos la clase [JUnitInitDBLocal] de la siguiente manera:
- líneas 3-5: referencias a las interfaces locales de EJB de la capa [DAO]
- línea 7: @BeforeClass anota el método que se ejecuta al iniciar la prueba JUnit
- líneas 10-13: inicialización del contenedor OpenEJB. Esta inicialización es específica y varía con cada contenedor EJB.
- línea 13: se cuenta con un contexto JNDI (Java Naming and Directory Interface) que permite acceder a los EJB mediante nombres. Con OpenEJB, la interfaz local de un EJB E se designa como ELocal y la interfaz remota como ERemote.
- líneas 15-17: se solicita al contexto JNDI una referencia sobre las interfaces locales de los EJB y [EmployeDao, CotisationDao, IndemniteDao].
![]() |
Se compila el proyecto (Build), se inicia el servidor MySQL si es necesario y se ejecuta la prueba JUnitInitDBLocal. Cabe recordar que el archivo [persistence.xml] se ha configurado para recrear las tablas en cada ejecución. Antes de ejecutar la prueba, es recomendable eliminar las tablas que puedan existir en las bases de datos MySQL y [dbpam_eclipselink].
![]() |
- en [1], en la pestaña [Services], se eliminan las tablas de la conexión de NetBeans establecida en el párrafo 6.2.1.
- En [2], la base de datos [dbpam_eclipselink] ya no tiene tablas
- En [3], se compila el proyecto
- en [4], se ejecuta la prueba JUnitInitDBLocal
![]() |
- en [5], la prueba se ha realizado con éxito
- en [6], se actualiza la conexión de NetBeans
- en [7], se ven las 4 tablas creadas por la capa JPA. El objetivo de la prueba era llenarlas. Se visualiza el contenido de una de ellas
![]() |
- en [8], el contenido de la tabla [EMPLOYES]
El contenedor OpenEJB mostró registros en la consola:
- líneas 2-3: los dos nombres JNDI de EJB y [CotisationDaoLocal],
- líneas 4-5: los dos nombres JNDI de EJB y [CotisationDaoRemote],
- líneas 7-8: los dos nombres JNDI de EJB y [EmployeDaoLocal],
- líneas 9-10: los dos nombres JNDI de EJB y [EmployeDaoRemote],
- líneas 12-13: los dos nombres JNDI de EJB y [IndemniteDaoLocal],
- líneas 14-15: los dos nombres JNDI de EJB y [EmployeDaoRemote].
Repetimos la misma prueba, esta vez utilizando la interfaz remota de EJB.
![]() |
En [1], la clase [JUnitInitDBLocal] se duplicó (copiar/pegar) en [JUnitInitDBRemote]. En esta clase, reemplazamos las interfaces locales por las interfaces remotas:
Una vez hecho esto, se puede ejecutar la nueva clase de prueba. Antes de eso, con la conexión de NetBeans [dbpam_eclipselink], elimine las tablas de la base de datos [dbpam_eclipselink].
![]() |
Con la conexión de NetBeans [dbpam_eclipselink], verifique que la base de datos se haya llenado.
6.2.4. Portación de la capa [metier]
Realizaremos la migración de la capa [metier] copiando los paquetes del proyecto [mv-pam-spring-hibernate] al proyecto [mv-pam-openejb-eclipselink].
![]() |
Los errores señalados anteriormente ([1]) se deben a que la capa [metier] copiada utiliza Spring y las bibliotecas de Spring ya no forman parte del proyecto.
6.2.4.1. El EJB [Metier]
Seguimos el mismo procedimiento que el descrito para EJB y [CotisationDao]. En primer lugar, creamos en [2] las interfaces local y remota del futuro EJB [Metier]. Ambas derivan de la interfaz inicial [IMetier].
Una vez hecho esto, en [3] modificamos la clase [Metier] para que se convierta en una EJB:
- línea 1: la anotación @Stateless convierte a la clase en una EJB
- línea 2: cada método de la clase se ejecutará en una transacción
- línea 3: el EJB [Metier] implementa las dos interfaces, la local y la remota, que acabamos de definir
- línea 7: el EJB [Metier] utilizará el EJB [CotisationDao] a través de su interfaz local. Esto significa que las capas [metier] y [DAO] deben ejecutarse en el mismo JVM.
- línea 6: la anotación @EJB hace que el contenedor EJB inyecte él mismo la referencia en la interfaz local de EJB [CotisationDao]. La otra forma que hemos encontrado es utilizar un contexto JNDI.
- Líneas 8-11: se utiliza el mismo mecanismo para los otros dos EJB de la capa [DAO].
6.2.4.2. Prueba de la capa [metier]
Nuestra capa [metier], implementada por un EJB, puede someterse a prueba. Comenzamos copiando el paquete [metier] de [Test Packages] del proyecto [mv-pam-spring-hibernate] al proyecto en construcción [1]:
![]() |
- en [1], el resultado de la copia
- en [2], se elimina la primera prueba
- en [3], la prueba restante se renombra como [JUnitMetierLocal]
La clase [JUnitMetierLocal] queda así:
- línea 4: una referencia a la interfaz local de EJB [Metier]
- líneas 8-12: configuración del contenedor OpenEJB idéntica a la realizada en la prueba de la capa [DAO]
- líneas 15-19: se solicitan al contexto JNDI de la línea 12, referencias sobre los 3 EJB de la capa [DAO] y sobre el EJB de la capa [metier]. Los EJB de la capa [DAO] se utilizarán para inicializar la base de datos, y el EJB de la capa [metier] para realizar pruebas de cálculo de salarios.
La ejecución de la prueba [JUnitMetierLocal] arroja el siguiente resultado [1]:
![]() |
En [2], se duplica [JUnitMetierLocal] en [JUnitMetierRemote] para probar, en esta ocasión, la interfaz remota de EJB y [Metier]. Se modifica el código de [JUnitMetierRemote] para utilizar esta interfaz remota. El resto no cambia.
- líneas 4 y 19: se utiliza la interfaz remota de EJB [Metier].
- líneas 15-17: se utilizan las interfaces remotas de la capa [DAO]
- líneas 34-35: dado que con las interfaces remotas los objetos que se intercambian entre el cliente y el servidor se pasan por valor, es necesario recuperar el resultado devuelto por el método create(Indemnite i). Esto no era obligatorio con las interfaces locales, donde los objetos se pasan por referencia.
Una vez hecho esto, se puede compilar el proyecto y ejecutar la prueba [JUnitMetierRemote]:
![]() |
6.2.5. Portabilidad de la capa [console]
Vamos a realizar la migración de la capa [console] copiando los paquetes del proyecto [mv-pam-spring-hibernate] al proyecto [mv-pam-openejb-eclipselink].
![]() |
Los errores señalados anteriormente ([1]) se deben a que la capa [metier] copiada utiliza Spring y las bibliotecas de Spring ya no forman parte del proyecto. En [2], la clase [Main] pasa a llamarse [MainLocal]. Utilizará la interfaz local de EJB [Metier].
El código de la clase [MainLocal] cambia de la siguiente manera:
Los cambios se encuentran en las líneas 13 a 25. Se trata de la forma de hacer referencia a la capa [metier], que cambia (líneas 17 a 22). No explicamos el nuevo código, ya que se ha visto en ejemplos anteriores. Una vez realizadas estas modificaciones, el proyecto ya no presenta errores (véase [3]).
Configuramos el proyecto para que se ejecute con los argumentos [1]:
![]() |
Para que la aplicación de consola se ejecute normalmente, debe haber datos en la base de datos. Para ello, hay que modificar el archivo [META-INF/persistence.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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_1_0.xsd">
<persistence-unit name="dbpam_eclipselinkPU" transaction-type="JTA">
<!-- el proveedor JPA es EclipseLink -->
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- entidades Jpa -->
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<!-- propiedades del proveedor EclipseLink -->
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
<!--
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
-->
</properties>
</persistence-unit>
</persistence>
La línea 14, que provocaba que las tablas de la base de datos se volvieran a crear en cada ejecución, se ha comentado. Se debe reconstruir el proyecto (Clean and Build) para que se aplique este cambio. Una vez hecho esto, se puede ejecutar el programa. Si todo sale bien, se obtendrá una salida en la consola similar a la siguiente:
Aquí hemos utilizado la interfaz local de la capa [metier]. Ahora utilizamos su interfaz remota en una segunda clase de consola:
![]() |
En [1], la clase [MainLocal] se duplicó en [MainRemote]. Se modifica el código de [MainRemote] para utilizar la interfaz remota de la capa [metier]:
Se realizan modificaciones en las líneas 2 y 8. Se configura el proyecto [2] para ejecutar la clase [MainRemote]. Su ejecución arroja los mismos resultados que anteriormente.
6.3. Conclusion
Hemos mostrado cómo migrar una arquitectura Spring / Hibernate a una arquitectura OpenEJB / EclipseLink.
La arquitectura Spring/Hibernate
![]() |
La arquitectura OpenEJB / EclipseLink
![]() |
La migración se pudo realizar sin demasiadas dificultades porque la aplicación inicial se había estructurado en capas. Es importante comprender este punto.




























