6. Versão 2: Arquitetura OpenEJB / JPA
6.1. Introdução aos princípios da migração
Apresentamos aqui os princípios que irão reger a migração de uma aplicação JPA / Spring / Hibernate para uma aplicação JPA / OpenEJB / EclipseLink. A criação dos projetos Maven será abordada no parágrafo 6.2.
6.1.1. As duas arquiteturas
A implementação atual com Spring / Hibernate
![]() |
A implementação a construir com OpenEJB / EclipseLink
![]() |
6.1.2. As bibliotecas dos projetos
- as camadas [DAO] e [metier] já não são instanciadas pelo Spring. São instanciadas pelo contentor OpenEJB.
- As bibliotecas do contentor Spring e a respetiva configuração são substituídas pelas bibliotecas do contentor OpenEJB e pela respetiva configuração.
- As bibliotecas da camada JPA / Hibernate são substituídas pelas da camada JPA / EclipseLink
6.1.3. Configuração da camada JPA / EclipseLink / OpenEJB
- O ficheiro [META-INF/persistence.xml], que configura a camada JPA, passa a ter o seguinte conteúdo:
- linha 3: as transações num contentor EJB são do tipo JTA (Java Transaction API). Com o Spring, eram do tipo RESOURCE_LOCAL.
- linha 9: a implementação JPA utilizada é a EclipseLink
- linhas 5-7: as entidades geridas pela camada JPA
- linhas 11-13: propriedades do provedor EclipseLink
- linha 12: a cada execução, as tabelas serão criadas
As características JDBC da fonte de dados JTA utilizada pelo contentor OpenEJB serão especificadas pelo seguinte ficheiro de configuração [conf/openejb.conf]:
- linha 3: utiliza-se o ID «Default JDBC Database» quando se trabalha com um contentor OpenEJB incorporado (embedded) na própria aplicação.
- linha 5: utilizamos uma base de dados MySQL [dbpam_eclipselink]
6.1.4. Implementação da camada [DAO] por meio de EJB
- As classes que implementam a camada [DAO] passam a ser EJB. Tomemos como exemplo a classe [CotisationDao]:
A interface [ICotisationDao] na versão Spring era a seguinte:
A EJB irá implementar esta mesma interface de duas formas diferentes: uma local e outra remota. A interface local pode ser utilizada por um cliente em execução no mesmo JVM, enquanto a interface remota pode ser utilizada por um cliente em execução noutro JVM.
A interface local:
- linha 6: a interface [ICotisationDaoLocal] herda da interface [ICotisationDao] para adotar todos os seus métodos. Não adiciona novos métodos.
- linha 5: a anotação @Local torna-a uma interface local para a EJB, que a irá implementar.
A interface remota:
- linha 6: a interface [ICotisationDaoRemote] herda da interface [ICotisationDao] para adotar todos os seus métodos. Não adiciona novos métodos.
- linha 5: a anotação @Remote torna-a uma interface remota para a EJB, que a irá implementar.
A camada [DAO] é implementada por um EJB que implementa ambas as interfaces (o que não é obrigatório):
- linha 1: a anotação @Stateless, que torna a classe um EJB
- linha 2: a anotação @TransactionAttribute, que faz com que cada método da classe seja executado no âmbito de uma transação.
- linha 5: a anotação @PersistenceContext que injeta na classe [CotisationDao] o EntityManager da camada JPA. É idêntica à que tínhamos na versão Spring.
Quando a interface local da camada [DAO] é utilizada, o cliente dessa interface é executado na mesma JVM.
![]() |
Acima, as camadas [metier] e [DAO] trocam objetos por referência. Quando uma camada altera o objeto partilhado, a outra camada deteta essa alteração.
Quando a interface remota da camada [DAO] é utilizada, o cliente dessa interface é normalmente executado noutra JVM.
![]() |
No exemplo acima, as camadas [metier] e [DAO] trocam objetos por valor (serialização do objeto trocado). Quando uma camada altera um objeto partilhado, a outra camada só deteta essa alteração se o objeto modificado lhe for reenviado.
6.1.5. Implementação da camada [metier] por uma EJB
- A classe que implementa a camada [metier] torna-se também um EJB que implementa uma interface local e remota. A interface inicial [IMetier] era a seguinte:
Cria-se uma interface local e uma interface remota a partir da interface anterior:
A interface EJB da camada [metier] implementa estas duas interfaces:
- linhas 1-2: definem um EJB cujos métodos são executados numa transação.
- linha 7: uma referência à interface local do EJB [CotisationDao].
- linha 6: a anotação @EJB solicita que o contentor EJB injete uma referência à interface local do EJB [CotisationDao].
- linhas 8-11: repete-se o mesmo procedimento para as interfaces locais de EJB, [EmployeDao] e [IndemniteDao].
Por fim, quando o EJB e o [Metier] forem instanciados, os campos das linhas 7, 9 e 11 serão inicializados com referências às interfaces locais dos três EJB da camada [DAO]. Partimos, portanto, do pressuposto de que as camadas [metier] e [DAO] serão executadas na mesma JVM.
![]() |
6.1.6. Os clientes do EJB
![]() |
No esquema acima, para comunicar com a camada [metier], a camada [ui] deve obter uma referência à interface remota da camada EJB da camada [metier].
![]() |
No esquema acima, para comunicar com a camada [metier], a camada [ui] deve obter uma referência à interface local da camada EJB da camada [metier]. O método para obter estas referências difere de um contentor para outro. Para o contentor OpenEJB, pode proceder-se da seguinte forma:
Referência na interface local:
- linhas 2-5: o contentor OpenEJB é inicializado.
- linha 5: existe um contexto JNDI (Java Naming and Directory Interface) que permite obter referências sobre os EJB. Cada EJB é designado por um nome JNDI:
- (continuação)
- para a interface local, acrescenta-se «Local» ao nome do EJB (linhas 7-9)
- para a interface remota, adiciona-se «Remote» ao nome do EJB
Com o Java EE 5, estas regras variam consoante o contentor EJB. Isto constitui uma dificuldade. O Java EE 6 introduziu uma notação JNDI compatível com todos os servidores de aplicações.
O código anterior recupera referências às interfaces locais dos EJB através dos seus nomes JNDI. Já referimos anteriormente que estas também podem ser obtidas através da anotação @EJB. Por isso, poderíamos querer escrever:
A anotação @EJB só é respeitada se pertencer a uma classe carregada pelo contentor EJB. Será o caso, por exemplo, da classe [Metier]. O código acima, por sua vez, pertencerá a uma classe de consola que não será carregada pelo contentor EJB. Por isso, somos obrigados a utilizar os nomes JNDI ou EJB.
Segue-se o código para obter uma referência à interface remota do EJB e do [Metier]:
6.2. Trabalho prático
Propõe-se a migração da aplicação NetBeans Spring / Hibernate para uma arquitetura OpenEJB / EclipseLink.
A implementação atual com Spring / Hibernate
![]() |
A implementação a construir com OpenEJB / EclipseLink
![]() |
6.2.1. Configuração da base de dados [dbpam_eclipselink]
Se não existir, crie a base de dados MySQL [dbpam_eclipselink]. Se existir, elimine todas as suas tabelas. Crie uma ligação do NetBeans a esta base de dados, tal como descrito no parágrafo 6.2.1.
6.2.2. Configuração inicial do projeto NetBeans
- Carregue o projeto Maven [mv-pam-spring-hibernate]
- Crie um novo projeto Maven Java [mv-pam-openejb-eclipselink] [1]
![]() |
- No separador [Files] [2], criar uma pasta [conf] [3] na raiz do projeto
- colocar nesta pasta o seguinte ficheiro [openejb.conf] [4]:
![]() |
- Crie a pasta [src / main/ resources/ META-INF] [5]
- colocar nela o ficheiro [persistence.xml] [6] seguinte:
<?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">
<!-- o fornecedor JPA é EclipseLink -->
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- entidades Jpa -->
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<!-- propriedades do fornecedor EclipseLink -->
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
- linha 12: solicitam-se registos detalhados ao EclipseLink,
- linha 13: as tabelas serão criadas aquando da instância da camada JPA,
- Adicionar as bibliotecas OpenEJB, EclipseLink e o controlador JDBC de MySQL ao ficheiro [pom.xml] do projeto:
<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>
- linhas 18-22: a dependência OpenEJB,
- linhas 30-39: as dependências EclipseLink,
- linhas 41-44: a dependência do controlador JDBC de MySQL
6.2.3. Portagem da camada [DAO]
Vamos realizar a migração da camada [DAO] através da cópia de pacotes do projeto [mv-pam-spring-hibernate] para o projeto [mv-pam-openejb-eclipselink].
- Copiar os pacotes [dao, exception, jpa]
![]() |
Os erros acima referidos devem-se ao facto de a camada [DAO] copiada utilizar o Spring e de as bibliotecas do Spring já não fazerem parte do projeto.
6.2.3.1. O EJB [CotisationDao]
Criamos as interfaces local e remota do futuro EJB [CotisationDao]:
A interface local ICotisationDaoLocal:
Para obter os pacotes corretos import, execute [clic droit sur le code / Fix Imports].
A interface remota ICotisationDaoRemote:
Em seguida, alteramos a classe [CotisationDao] para a transformar num EJB:
O import que esta classe gerava no framework Spring desaparece. Criar um [Clean and Build] do projeto:
![]() |
No [1], já não há erros na classe [CotisationDao].
6.2.3.2. Os EJB, [EmployeDao] e [IndemniteDao]
Repete-se o mesmo procedimento para os outros elementos da camada [DAO]:
- as interfaces IEmployeDaoLocal e IEmployeDaoRemote, derivadas de IEmployeDao
- EJB e EmployeDao, que implementam estas duas interfaces
- interfaces IIndemniteDaoLocal e IIndemniteDaoRemote, derivadas de IIndemniteDao
- EJB e IndemniteDao, que implementam estas duas interfaces
Feito isto, já não existem erros no projeto [2].
6.2.3.3. A classe [PamException]
A classe [PamException] permanece tal como estava, com uma única diferença:
A linha 5 foi adicionada. Para obter os import corretos, altere para [Fix imports].
Para compreender a anotação da linha 5, é preciso lembrar que cada método dos EJB da nossa camada [DAO]:
- é executado numa transação iniciada e concluída pelo contentor EJB
- lança uma exceção do tipo [PamException] assim que algo corre mal
![]() |
Quando a camada [metier] chama um método M da camada [DAO], essa chamada é interceptada pelo contentor EJB. Tudo acontece como se houvesse uma classe intermédia entre a camada [metier] e a camada [DAO], aqui denominada [Proxy EJB], que intercepta todas as chamadas para a camada [DAO]. Quando a chamada ao método M da camada [DAO] é interceptada, o proxy EJB inicia uma transação e, em seguida, passa o controlo ao método M da camada [DAO], que é então executado nessa transação. O método M termina com ou sem exceção.
- Se o método M terminar sem exceção, a execução volta para o proxy EJB, que encerra a transação, validando-a através de um commit. O fluxo de execução é então devolvido ao método chamador da camada [metier]
- Se o método M terminar com uma exceção, a execução volta para o proxy EJB, que encerra a transação invalidando-a através de um rollback. Além disso, encapsula essa exceção num tipo EJBException. O fluxo de execução é então devolvido ao método chamador da camada [metier], que recebe, assim, um EJBException. A anotação na linha 5 acima impede este encapsulamento. A camada [metier] receberá, portanto, um PamException. Além disso, o atributo rollback=true indica ao proxy EJB que, quando receber um PamException, deve invalidar a transação.
6.2.3.4. Teste da camada [DAO]
A nossa camada [DAO], implementada por EJB, pode ser testada. Começamos por copiar o pacote [dao] de [Test Packages] do projeto [mv-pam-springhibernate] para o projeto em desenvolvimento [1]:
![]() |
Mantemos apenas o teste [JUnitInitDB], que inicializa a base de dados com alguns dados do [2]. Renomeamos a classe [ JUnitInitDbLocal] para [3]. A classe [JUnitInitDBLocal] utilizará a interface local da classe EJB da camada [DAO].
Primeiro, alteramos a classe [JUnitInitDBLocal] da seguinte forma:
- linhas 3-5: referências às interfaces locais de EJB da camada [DAO]
- linha 7: @BeforeClass anota o método executado no início do teste JUnit
- linhas 10-13: inicialização do contentor OpenEJB. Esta inicialização é específica e varia consoante cada contentor EJB.
- linha 13: existe um contexto JNDI (Java Naming and Directory Interface) que permite aceder aos EJB através de nomes. Com o OpenEJB, a interface local de um EJB E é designada por ELocal e a interface remota por ERemote.
- linhas 15-17: solicita-se ao contexto JNDI uma referência às interfaces locais dos EJB e [EmployeDao, CotisationDao, IndemniteDao].
![]() |
Compila-se o projeto (Build), inicia-se o servidor MySQL, se necessário, e executa-se o teste JUnitInitDBLocal. Recorde-se que o ficheiro [persistence.xml] foi configurado para recriar as tabelas a cada execução. Antes da execução do teste, é preferível eliminar quaisquer tabelas existentes na base de dados MySQL e [dbpam_eclipselink].
![]() |
- em [1], no separador [Services], eliminam-se as tabelas da ligação ao NetBeans estabelecida no parágrafo 6.2.1.
- em [2], a base de dados [dbpam_eclipselink] já não tem tabelas
- em [3], o projeto é compilado
- em [4], o teste JUnitInitDBLocal é executado
![]() |
- em [5], o teste foi bem-sucedido
- em [6], atualiza-se a ligação do NetBeans
- em [7], visualizam-se as 4 tabelas criadas pela camada JPA. O objetivo do teste era preenchê-las. Visualiza-se o conteúdo de uma delas
![]() |
- em [8], o conteúdo da tabela [EMPLOYES]
O contentor OpenEJB apresentou registos na consola:
- linhas 2-3: os dois nomes JNDI do EJB e do [CotisationDaoLocal],
- linhas 4-5: os dois nomes JNDI do EJB e do [CotisationDaoRemote],
- linhas 7-8: os dois nomes JNDI do EJB [EmployeDaoLocal],
- linhas 9-10: os dois nomes JNDI do EJB [EmployeDaoRemote],
- linhas 12-13: os dois nomes JNDI do EJB [IndemniteDaoLocal],
- linhas 14-15: os dois nomes JNDI do EJB e do [EmployeDaoRemote].
Repetimos o mesmo teste, utilizando desta vez a interface remota do EJB.
![]() |
No [1], a classe [JUnitInitDBLocal] foi duplicada (copiar/colar) para o [JUnitInitDBRemote]. Nesta classe, substituímos as interfaces locais pelas interfaces remotas:
Feito isto, a nova classe de teste pode ser executada. Antes disso, com a ligação NetBeans [dbpam_eclipselink], elimine as tabelas da base de dados [dbpam_eclipselink].
![]() |
Com a ligação do NetBeans [dbpam_eclipselink], verifique se a base de dados foi preenchida.
6.2.4. Portagem da camada [metier]
Vamos realizar a migração da camada [metier] através da cópia de pacotes do projeto [mv-pam-spring-hibernate] para o projeto [mv-pam-openejb-eclipselink].
![]() |
Os erros acima referidos [1] devem-se ao facto de a camada [metier] copiada utilizar o Spring e de as bibliotecas do Spring já não fazerem parte do projeto.
6.2.4.1. O EJB [Metier]
Seguimos o mesmo procedimento descrito para o EJB [CotisationDao]. Primeiro, criamos no [2] as interfaces local e remota do futuro EJB [Metier]. Ambas derivam da interface inicial [IMetier].
Feito isto, no [3], alteramos a classe [Metier] para que se torne um EJB:
- linha 1: a anotação @Stateless transforma a classe numa EJB
- linha 2: cada método da classe será executado numa transação
- linha 3: o EJB [Metier] implementa as duas interfaces, local e remota, que acabámos de definir
- linha 7: o EJB [Metier] irá utilizar o EJB [CotisationDao] através da interface local deste último. Isto significa que as camadas [metier] e [DAO] têm de ser executadas na mesma JVM.
- linha 6: a anotação @EJB faz com que o contentor EJB injete ele próprio a referência na interface local do EJB [CotisationDao]. A outra forma que encontrámos consiste em utilizar um contexto JNDI.
- linhas 8-11: o mesmo mecanismo é utilizado para os outros dois EJB da camada [DAO].
6.2.4.2. Teste da camada [metier]
A nossa camada [metier], implementada por um EJB, pode ser testada. Começamos por copiar o pacote [metier] de [Test Packages] do projeto [mv-pam-spring-hibernate] para o projeto em desenvolvimento [1]:
![]() |
- para [1], o resultado da cópia
- para [2], elimina-se o primeiro teste
- em [3], o teste restante é renomeado para [JUnitMetierLocal]
A classe [JUnitMetierLocal] passa a ter o seguinte aspeto:
- linha 4: uma referência à interface local do EJB [Metier]
- linhas 8-12: configuração do contentor OpenEJB idêntica à realizada no teste da camada [DAO]
- linhas 15-19: solicitam-se ao contexto JNDI da linha 12, referências aos 3 EJB da camada [DAO] e ao EJB da camada [metier]. Os EJB da camada [DAO] servirão para inicializar a base de dados, e o EJB da camada [metier] para realizar testes de cálculo de salários.
A execução do teste [JUnitMetierLocal] produz o seguinte resultado [1]:
![]() |
No [2], duplicamos o [JUnitMetierLocal] para o [JUnitMetierRemote], para testar, desta vez, a interface remota do EJB e do [Metier]. O código do [JUnitMetierRemote] é alterado para utilizar esta interface remota. O resto permanece inalterado.
- linhas 4 e 19: utiliza-se a interface remota do EJB [Metier].
- linhas 15-17: utilizam-se as interfaces remotas da camada [DAO]
- linhas 34-35: uma vez que, com as interfaces remotas, os objetos trocados entre o cliente e o servidor são passados por valor, é necessário recuperar o resultado devolvido pelo método create(Indemnite i). Isto não era obrigatório com as interfaces locais, onde os objetos são passados por referência.
Feito isto, o projeto pode ser compilado e o teste [JUnitMetierRemote] executado:
![]() |
6.2.5. Portagem da camada [console]
Vamos proceder à portabilidade da camada [console], copiando os pacotes do projeto [mv-pam-spring-hibernate] para o projeto [mv-pam-openejb-eclipselink].
![]() |
Os erros acima referidos, [1], devem-se ao facto de a camada [metier] copiada utilizar o Spring e de as bibliotecas do Spring já não fazerem parte do projeto. No [2], a classe [Main] é renomeada para [MainLocal]. Esta irá utilizar a interface local do EJB [Metier].
O código da classe [MainLocal] evolui da seguinte forma:
As alterações ocorrem nas linhas 13 a 25. Trata-se da forma de fazer referência à camada [metier], que sofre alterações (linhas 17 a 22). Não explicamos o novo código, que já foi abordado em exemplos anteriores. Depois de efetuadas estas alterações, o projeto já não apresenta erros (ver [3]).
Configuramos o projeto para que seja executado com os argumentos [1]:
![]() |
Para que a aplicação de consola seja executada normalmente, é necessário que existam dados na base de dados. Para tal, é necessário alterar o ficheiro [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">
<!-- o fornecedor JPA é EclipseLink -->
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- entidades Jpa -->
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<!-- propriedades do fornecedor EclipseLink -->
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
<!--
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
-->
</properties>
</persistence-unit>
</persistence>
A linha 14, que fazia com que as tabelas da base de dados fossem recriadas a cada execução, é colocada em comentário. O projeto deve ser reconstruído (Clean and Build) para que esta alteração seja aplicada. Feito isto, é possível executar o programa. Se tudo correr bem, obtém-se uma saída na consola semelhante à seguinte:
Utilizámos aqui a interface local da camada [metier]. Passamos agora a utilizar a sua interface remota numa segunda classe de consola:
![]() |
Em [1], a classe [MainLocal] foi duplicada em [MainRemote]. O código de [MainRemote] foi alterado para utilizar a interface remota da camada [metier]:
As alterações são efetuadas nas linhas 2 e 8. O projeto [2] é configurado para executar a classe [MainRemote]. A sua execução produz os mesmos resultados que anteriormente.
6.3. Conclusion
Mostrámos como migrar uma arquitetura Spring/Hibernate para uma arquitetura OpenEJB/EclipseLink.
A arquitetura Spring/Hibernate
![]() |
A arquitetura OpenEJB / EclipseLink
![]() |
A migração pôde ser realizada sem grandes dificuldades porque a aplicação inicial tinha sido estruturada em camadas. É importante compreender este ponto.




























