6. Versão 2: Arquitetura OpenEJB / JPA
6.1. Introdução aos princípios de portabilidade
Apresentamos aqui os princípios que regerão a portabilidade de uma aplicação JPA / Spring / Hibernate para uma aplicação JPA / OpenEJB / EclipseLink. Esperaremos até à Secção 6.2 para criar os projetos Maven.
6.1.1. As duas arquiteturas
A implementação atual com Spring / Hibernate
![]() |
A implementação a ser construída com OpenEJB / EclipseLink
![]() |
6.1.2. Bibliotecas do projeto
- As camadas [DAO] e [business] já não são instanciadas pelo Spring. São instanciadas pelo contentor OpenEJB.
- As bibliotecas do contentor Spring e a sua configuração são substituídas pelas bibliotecas do contentor OpenEJB e pela sua 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 é o EclipseLink
- Linhas 5–7: Entidades geridas pela camada JPA
- Linhas 11–13: Propriedades do provedor EclipseLink
- linha 12: serão criadas tabelas em cada execução
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: Utilizamos o ID «Banco de dados JDBC padrão» ao trabalhar com um contêiner OpenEJB incorporado na própria aplicação.
- Linha 5: utilizamos uma base de dados MySQL [dbpam_eclipselink]
6.1.4. Implementação da camada [DAO] utilizando EJBs
- As classes que implementam a camada [DAO] tornam-se EJBs. Tomemos como exemplo a classe [CotisationDao]:
A interface [ICotisationDao] na versão Spring era a seguinte:
O EJB irá implementar esta mesma interface de duas formas diferentes: uma local e outra remota. A interface local pode ser utilizada por um cliente a executar-se na mesma JVM, enquanto a interface remota pode ser utilizada por um cliente a executar-se numa JVM diferente.
A interface local:
- Linha 6: A interface [ICotisationDaoLocal] herda da interface [ICotisationDao] para adotar todos os seus métodos. Não adiciona nenhum novo.
- linha 5: a anotação @Local torna-a uma interface local para o 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 nenhum novo.
- linha 5: A anotação @Remote torna-a uma interface remota para o EJB que a irá implementar.
A camada [DAO] é implementada por um EJB que implementa ambas as interfaces (isto não é obrigatório):
- linha 1: a anotação @Stateless, que torna a classe um EJB
- linha 2: a anotação @TransactionAttribute, que garante que todos os métodos da classe serão executados dentro de uma transação.
- linha 5: a anotação @PersistenceContext, que injeta o EntityManager da camada JPA na classe [CotisationDao]. É idêntico ao que tínhamos na versão Spring.
Quando a interface local da camada [DAO] é utilizada, o cliente desta interface é executado na mesma JVM.
![]() |
Acima, as camadas [business] e [DAO] trocam objetos por referência. Quando uma camada altera o objeto partilhado, a outra camada percebe essa alteração.
Quando a interface remota da camada [DAO] é utilizada, o cliente desta interface funciona normalmente noutra JVM.
![]() |
No diagrama acima, as camadas [business] e [DAO] trocam objetos por valor (serialização do objeto trocado). Quando uma camada altera um objeto partilhado, a outra camada só vê essa alteração se o objeto modificado lhe for devolvido.
6.1.5. Implementação da camada [business] utilizando um EJB
- A classe que implementa a camada [business] torna-se também um EJB que implementa uma interface local e remota. A interface [IMetier] inicial era a seguinte:
Criamos uma interface local e uma interface remota com base na interface anterior:
O EJB na camada [de negócios] implementa estas duas interfaces:
- Linhas 1-2: definem um EJB no qual cada método é executado dentro de uma transação.
- linha 7: uma referência à interface local do EJB [CotisationDao].
- linha 6: a anotação @EJB instrui o contentor EJB a injetar uma referência à interface local do EJB [CotisationDao].
- Linhas 8–11: Fazemos o mesmo para as interfaces locais dos EJBs [EmployeeDao] e [CompensationDao].
Por fim, quando o EJB [Metier] for instanciado, os campos nas linhas 7, 9 e 11 serão inicializados com referências às interfaces locais dos três EJBs na camada [DAO]. Estamos, portanto, a assumir aqui que as camadas [business] e [DAO] serão executadas na mesma JVM.
![]() |
6.1.6. Clientes EJB
![]() |
No diagrama acima, para comunicar com a camada [business], a camada [ui] deve obter uma referência à interface remota do EJB na camada [business].
![]() |
No diagrama acima, para comunicar com a camada [business], a camada [UI] deve obter uma referência à interface local do EJB da camada [business]. O método para obter estas referências varia de um contentor para outro. Para o contentor OpenEJB, pode proceder da seguinte forma:
Referência à interface local:
- Linhas 2–5: O contentor OpenEJB é inicializado.
- Linha 5: Temos um contexto JNDI (Java Naming and Directory Interface) que nos permite obter referências aos EJBs. Cada EJB é identificado por um nome JNDI:
- (continuação)
- para a interface local, adicione «Local» ao nome do EJB (linhas 7-9)
- Para a interface remota, adicionamos "Remote" ao nome do EJB
Com o Java EE 5, estas regras variam consoante o contentor EJB. Isto constitui um desafio. O Java EE 6 introduziu uma notação JNDI que é portátil em todos os servidores de aplicações.
O código anterior recupera referências às interfaces locais dos EJBs através dos seus nomes JNDI. Como mencionámos anteriormente, estas também podem ser obtidas através da anotação @EJB. Por isso, poderíamos escrever:
A anotação @EJB só é respeitada se pertencer a uma classe carregada pelo contentor EJB. Este será o caso da classe [Metier], por exemplo. O código acima, no entanto, pertence a uma classe de consola que não será carregada pelo contentor EJB. Somos, portanto, obrigados a utilizar os nomes JNDI dos EJBs.
Segue-se o código para obter uma referência à interface remota do EJB [Metier]:
6.2. Exercício prático
Propomos migrar a aplicação NetBeans Spring/Hibernate para uma arquitetura OpenEJB/EclipseLink.
A implementação atual com Spring / Hibernate
![]() |
A implementação a ser construída 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, conforme descrito na secção 6.2.1.
6.2.2. Configuração inicial do projeto NetBeans
- Carregue o projeto Maven [mv-pam-spring-hibernate]
- Crie um novo projeto Java do Maven [mv-pam-openejb-eclipselink] [1]
![]() |
- No separador [Ficheiros] [2], crie uma pasta [conf] [3] na raiz do projeto
- Coloque o seguinte ficheiro [openejb.conf] [4] nesta pasta:
![]() |
- Crie a pasta [src/main/resources/META-INF] [5]
- Coloque o seguinte ficheiro [persistence.xml] [6] nessa pasta:
<?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">
<!-- the supplier JPA is EclipseLink -->
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- jpa entities -->
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<!-- properties provider 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: Solicitamos registos detalhados ao EclipseLink,
- linha 13: as tabelas serão criadas quando a camada JPA for instanciada,
- Adicione as bibliotecas OpenEJB e EclipseLink, bem como o controlador JDBC do 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 do OpenEJB,
- linhas 30–39: as dependências do EclipseLink,
- linhas 41-44: a dependência do controlador JDBC do MySQL
6.2.3. Portar a camada [DAO]
Iremos portar a camada [DAO] copiando pacotes do projeto [mv-pam-spring-hibernate] para o projeto [mv-pam-openejb-eclipselink].
- Copie os pacotes [dao, exception, jpa]
![]() |
Os erros relatados acima devem-se ao facto de a camada [DAO] copiada utilizar o Spring e as bibliotecas do Spring já não fazerem parte do projeto.
6.2.3.1. O EJB [CotisationDao]
Criamos as interfaces locais e remotas para o futuro EJB [CotisationDao]:
A interface local ICotisationDaoLocal:
Para garantir que as importações de pacotes estão corretas, clique com o botão direito do rato no código e selecione [Corrigir importações].
A interface remota ICotisationDaoRemote:
Em seguida, modificamos a classe [CotisationDao] para a transformar num EJB:
A importação que esta classe fez na estrutura Spring desaparece. Execute um [Limpar e Compilar] no projeto:
![]() |
Em [1], já não existem erros na classe [CotisationDao].
6.2.3.2. Os EJBs [EmployeDao] e [IndemniteDao]
Repetimos o mesmo processo para os outros elementos da camada [DAO]:
- interfaces IEmployeDaoLocal e IEmployeDaoRemote derivadas de IEmployeDao
- EJB `EmployeDao` que implementa estas duas interfaces
- interfaces IIndemniteDaoLocal e IIndemniteDaoRemote derivadas de IIndemniteDao
- EJB IndemniteDao implementando estas duas interfaces
Depois de fazer isto, não há mais erros no projeto [2].
6.2.3.3. A classe [PamException]
A classe [PamException] permanece a mesma de antes, com uma pequena alteração:
A linha 5 foi adicionada. Para obter as importações corretas, clique em [Corrigir importações].
Para compreender a anotação na linha 5, lembre-se de que todos os métodos nos EJBs da nossa camada [DAO]:
- é executado dentro de uma transação iniciada e encerrada pelo contentor EJB
- lança uma [PamException] assim que algo corre mal
![]() |
Quando a camada [business] chama um método M da camada [DAO], esta chamada é interceptada pelo contentor EJB. É como se houvesse uma classe intermédia entre a camada [business] e a camada [DAO] — aqui denominada [EJB Proxy] — a interceptar todas as chamadas à camada [DAO]. Quando a chamada ao método M da camada [DAO] é interceptada, o EJB Proxy inicia uma transação e, em seguida, passa o controlo para o método M da camada [DAO], que é então executado dentro dessa transação. O método M pode ser concluído com ou sem uma exceção.
- Se o método M for concluído sem uma exceção, o controlo retorna ao EJB Proxy, que encerra a transação ao confirmá-la. O controlo é então devolvido ao método de chamada na camada [business]
- Se o método M terminar com uma exceção, a execução retorna ao proxy EJB, que encerra a transação revertendo-a. Além disso, ele encapsula essa exceção em uma EJBException. A execução regressa então ao método de chamada na camada [business], que recebe, por conseguinte, uma EJBException. A anotação na linha 5 acima impede este encapsulamento. A camada [business] receberá, portanto, uma PamException. Além disso, o atributo rollback=true instrui o proxy EJB para que, ao receber uma PamException, reverta a transação.
6.2.3.4. Testar a camada [DAO]
A nossa camada [DAO] implementada por EJBs pode ser testada. Começamos por copiar o pacote [dao] de [Test Packages] no projeto [mv-pam-springhibernate] para o projeto atualmente em desenvolvimento [1]:
![]() |
Mantemos apenas o teste [JUnitInitDB], que inicializa a base de dados com alguns dados [2]. Renomeamos a classe [JUnitInitDbLocal] [3]. A classe [JUnitInitDBLocal] utilizará a interface local dos EJBs da camada [DAO].
Primeiro, modificamos a classe [JUnitInitDBLocal] da seguinte forma:
- linhas 3-5: referências às interfaces locais dos EJBs na camada [DAO]
- linha 7: @BeforeClass anota o método executado quando o teste JUnit começa
- linhas 10-13: inicialização do contentor OpenEJB. Esta inicialização é proprietária e varia consoante o contentor EJB.
- linha 13: temos um contexto JNDI (Java Naming and Directory Interface) que permite o acesso aos EJBs através de nomes. Com o OpenEJB, a interface local de um EJB é designada por ELocal e a interface remota por ERemote.
- Linhas 15–17: solicitamos uma referência às interfaces locais dos EJBs [EmployeDao, CotisationDao, IndemniteDao] a partir do contexto JNDI.
![]() |
Compile o projeto, inicie o servidor MySQL, se necessário, e execute o teste JUnitInitDBLocal. Note que o ficheiro [persistence.xml] foi configurado para recriar as tabelas em cada execução. Antes de executar o teste, é melhor eliminar quaisquer tabelas na base de dados MySQL [dbpam_eclipselink].
![]() |
- Em [1], no separador [Serviços], elimine as tabelas da ligação NetBeans estabelecida na secção 6.2.1.
- Em [2], a base de dados [dbpam_eclipselink] já não contém quaisquer tabelas
- Em [3], o projeto é compilado
- Em [4], o teste JUnitInitDBLocal é executado
![]() |
- Em [5], o teste foi aprovado
- Em [6], atualize a ligação do NetBeans
- em [7], vemos as 4 tabelas criadas pela camada JPA. O objetivo do teste era preenchê-las. Visualizamos o conteúdo de uma delas
![]() |
- em [8], o conteúdo da tabela [EMPLOYEES]
O contentor OpenEJB apresentou registos na consola:
- linhas 2-3: os dois nomes JNDI do EJB [CotisationDaoLocal],
- linhas 4-5: os dois nomes JNDI do EJB [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 [EmployeeDaoRemote].
Executamos o mesmo teste novamente, desta vez utilizando a interface remota dos EJBs.
![]() |
Em [1], a classe [JUnitInitDBLocal] foi duplicada (copiar/colar) para [JUnitInitDBRemote]. Nesta classe, substituímos as interfaces locais pelas interfaces remotas:
Depois de concluído este passo, a nova classe de teste pode ser executada. Antes de o fazer, utilizando a ligação do NetBeans [dbpam_eclipselink], elimine as tabelas da base de dados [dbpam_eclipselink].
![]() |
Com a ligação NetBeans [dbpam_eclipselink], verifique se a base de dados foi preenchida.
6.2.4. Portar a camada [de negócios]
Iremos portar a camada [business] copiando pacotes do projeto [mv-pam-spring-hibernate] para o projeto [mv-pam-openejb-eclipselink].
![]() |
Os erros relatados acima [1] devem-se ao facto de a camada [business] copiada utilizar o Spring e as bibliotecas do Spring já não fazerem parte do projeto.
6.2.4.1. O EJB [Business]
Seguimos o mesmo procedimento descrito para o EJB [CotisationDao]. Primeiro, em [2], criamos as interfaces local e remota para o futuro EJB [Metier]. Ambas derivam da interface inicial [IMetier].
Depois de fazer isto, em [3] modificamos a classe [Business] para que se torne um EJB:
- linha 1: a anotação @Stateless torna a classe um EJB
- linha 2: cada método da classe será executado dentro de uma transação
- linha 3: o EJB [Metier] implementa tanto a interface local como a remota que acabámos de definir
- linha 7: o EJB [Metier] utilizará o EJB [CotisationDao] através da sua interface local. Isto significa que as camadas [business] e [DAO] devem ser executadas na mesma JVM.
- Linha 6: A anotação @EJB garante que o contentor EJB injeta a referência à interface local do próprio EJB [CotisationDao]. A outra abordagem que encontramos consiste em utilizar um contexto JNDI.
- Linhas 8–11: O mesmo mecanismo é utilizado para os outros dois EJBs na camada [DAO].
6.2.4.2. Testar a camada [business]
A nossa camada [business], implementada por um EJB, pode ser testada. Começamos por copiar o pacote [business] de [Test Packages] no projeto [mv-pam-spring-hibernate] para o projeto atualmente em construção [1]:
![]() |
- em [1], o resultado da cópia
- em [2], eliminamos 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 à utilizada no teste da camada [DAO]
- linhas 15–19: solicitamos referências do contexto JNDI na linha 12 para os três EJBs na camada [DAO] e para o EJB na camada [business]. Os EJBs na camada [DAO] serão utilizados para inicializar a base de dados, e o EJB na camada [business] será utilizado para realizar testes de cálculo de salários.
A execução do teste [JUnitMetierLocal] produz o seguinte resultado [1]:
![]() |
Em [2], duplicamos o [JUnitMetierLocal] como [JUnitMetierRemote] para testar, desta vez, a interface remota do EJB [Metier]. O código do [JUnitMetierRemote] é modificado para utilizar esta interface remota. O resto permanece inalterado.
- linhas 4 e 19: usamos a interface remota do EJB [Business].
- linhas 15–17: utilizamos as interfaces remotas da camada [DAO]
- linhas 34–35: Como nas interfaces remotas os objetos trocados entre o cliente e o servidor são passados por valor, temos de recuperar o resultado devolvido pelo método create(Indemnite i). Isto não era necessário com interfaces locais, onde os objetos são passados por referência.
Depois de feito isto, o projeto pode ser compilado e o teste [JUnitMetierRemote] executado:
![]() |
6.2.5. Portar a camada [console]
Iremos portar a camada [console] copiando pacotes do projeto [mv-pam-spring-hibernate] para o projeto [mv-pam-openejb-eclipselink].
![]() |
Os erros relatados acima [1] resultam do facto de a camada [business] copiada utilizar o Spring, e as bibliotecas do Spring já não fazerem parte do projeto. Em [2], a classe [Main] é renomeada para [MainLocal]. Esta irá utilizar a interface local do EJB [Business].
O código da classe [MainLocal] é alterado da seguinte forma:
As alterações encontram-se nas linhas 13–25. É assim que obtemos uma referência à camada [business], que sofreu alterações (linhas 17–22). Não iremos explicar o novo código, uma vez que já foi abordado em exemplos anteriores. Após estas alterações, o projeto já não apresenta erros (ver [3]).
Configuramos o projeto para ser executado com argumentos [1]:
![]() |
Para que a aplicação de consola funcione normalmente, é necessário que existam dados na base de dados. Para tal, é necessário modificar o ficheiro [META-INF/persistence.xml]:
A linha 14, que fazia com que as tabelas da base de dados fossem recriadas em cada execução, foi comentada. O projeto deve ser recompilado (Limpar e Compilar) para que esta alteração tenha efeito. Depois de feito isto, o programa pode ser executado. Se tudo correr bem, a saída da consola será semelhante à 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">
<!-- the supplier JPA is EclipseLink -->
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- jpa entities -->
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<!-- properties provider EclipseLink -->
<properties>
<property name="eclipselink.logging.level" value="FINE"/>
<!--
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
-->
</properties>
</persistence-unit>
</persistence>
Aqui, utilizámos a interface local da camada [business]. Agora, utilizamos a sua interface remota numa segunda classe de consola:
![]() |
Em [1], a classe [MainLocal] foi duplicada para [MainRemote]. O código em [MainRemote] foi modificado para utilizar a interface remota da camada [business]:
Foram feitas alterações nas linhas 2 e 8. O projeto está configurado [2] para executar a classe [MainRemote]. A sua execução produz os mesmos resultados que anteriormente.
6.3. Conclusão
Demonstrámos como migrar uma arquitetura Spring/Hibernate para uma arquitetura OpenEJB/EclipseLink.
A arquitetura Spring/Hibernate
![]() |
A arquitetura OpenEJB/EclipseLink
![]() |
O processo de portabilidade decorreu sem problemas porque a aplicação original tinha sido estruturada em camadas. É importante compreender este ponto.




























