6. Versão 2: Arquitetura OpenEJB / JPA
6.1. Introdução aos princípios da 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. A criação dos projetos Maven será realizada no parágrafo 6.2.
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. As bibliotecas dos projetos
- as camadas [DAO] e [metier] não são mais instanciadas pelo Spring. Elas são instanciadas pelo contêiner OpenEJB.
- As bibliotecas do contêiner Spring e sua configuração são substituídas pelas bibliotecas do contêiner OpenEJB e 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 arquivo [META-INF/persistence.xml], que configura a camada JPA, passa a ter o seguinte conteúdo:
- linha 3: as transações em um contêiner EJB são do tipo JTA (Java Transaction API). Elas eram do tipo RESOURCE_LOCAL com o Spring.
- linha 9: a implementação JPA utilizada é EclipseLink
- linhas 5-7: as entidades gerenciadas 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 contêiner OpenEJB serão especificadas pelo seguinte arquivo de configuração [conf/openejb.conf]:
- linha 3: utiliza-se o ID “Default JDBC Database” ao trabalhar com um contêiner 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 implementará essa 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 em outro JVM.
A interface local:
- linha 6: a interface [ICotisationDaoLocal] herda da interface [ICotisationDao] para adotar todos os seus métodos. Ela não adiciona novos métodos.
- linha 5: a anotação @Local torna-a uma interface local para a EJB, que a implementará.
A interface remota:
- linha 6: a interface [ICotisationDaoRemote] herda da interface [ICotisationDao] para adotar todos os seus métodos. Ela não adiciona novos métodos.
- linha 5: a anotação @Remote torna-a uma interface remota para a EJB, que a implementará.
A camada [DAO] é implementada por um EJB que implementa as duas interfaces (isso 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 dentro de uma transação.
- linha 5: a anotação @PersistenceContext, que injeta na classe [CotisationDao] o EntityManager da camada JPA. Ela é 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 compartilhado, a outra camada percebe essa alteração.
Quando a interface remota da camada [DAO] é utilizada, o cliente dessa interface geralmente é executado em outra JVM.
![]() |
No exemplo acima, as camadas [metier] e [DAO] trocam objetos por valor (serialização do objeto trocado). Quando uma camada altera um objeto compartilhado, a outra camada só percebe essa alteração se o objeto modificado for reenviado a ela.
6.1.5. Implementação da camada [metier] por uma EJB
- A classe que implementa a camada [metier] também se torna 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 essas duas interfaces:
- linhas 1-2: definem um EJB cujos métodos são executados em uma transação.
- linha 7: uma referência à interface local do EJB [CotisationDao].
- linha 6: a anotação @EJB solicita que o contêiner EJB injete uma referência à interface local do EJB [CotisationDao].
- linhas 8-11: repetimos 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]. Portanto, parte-se aqui da hipótese de que as camadas [metier] e [DAO] serão executadas na mesma JVM.
![]() |
6.1.6. Os clientes do EJB
![]() |
No esquema acima, para se comunicar com a camada [metier], a camada [ui] deve obter uma referência à interface remota do EJB da camada [metier].
![]() |
No esquema acima, para se 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 essas referências varia de um contêiner para outro. Para o contêiner OpenEJB, pode-se proceder da seguinte forma:
Referência na interface local:
- linhas 2-5: o contêiner OpenEJB é inicializado.
- linha 5: temos 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, essas regras mudam de acordo com o contêiner EJB. Isso representa uma dificuldade. O Java EE 6 introduziu uma notação JNDI compatível com todos os servidores de aplicativos.
O código anterior recupera referências às interfaces locais do EJB por meio de seus nomes JNDI. Mencionamos anteriormente que essas referências também podem ser obtidas por meio da anotação @EJB. Portanto, poderíamos querer escrever:
A anotação @EJB só será reconhecida se pertencer a uma classe carregada pelo contêiner EJB. Esse será o caso da classe [Metier], por exemplo. O código acima, por sua vez, pertencerá a uma classe de console que não será carregada pelo contêiner EJB. Portanto, somos obrigados a utilizar os nomes JNDI ou EJB.
Abaixo, o código para obter uma referência à interface remota do EJB e do [Metier]:
6.2. Trabalho prático
Propõe-se portar a aplicação NetBeans Spring/Hibernate para uma arquitetura OpenEJB / EclipseLink.
A implementação atual com Spring / Hibernate
![]() |
A implementação a ser desenvolvida com OpenEJB / EclipseLink
![]() |
6.2.1. Configuração do banco de dados [dbpam_eclipselink]
Se ela não existir, crie o banco de dados MySQL [dbpam_eclipselink]. Se ela existir, exclua todas as suas tabelas. Crie uma conexão do NetBeans com esse banco de dados, conforme descrito no parágrafo 6.2.1.
6.2.2. Configuração inicial do projeto no NetBeans
- Carregue o projeto Maven [mv-pam-spring-hibernate]
- Crie um novo projeto Maven Java [mv-pam-openejb-eclipselink] [1]
![]() |
- na guia [Files] [2], crie uma pasta [conf] [3] na raiz do projeto
- colocar nessa pasta o seguinte arquivo [openejb.conf] [4]:
![]() |
- Crie a pasta [src / main/ resources/ META-INF] [5]
- colocar nela os arquivos [persistence.xml] e [6] a seguir:
<?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 provedor 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 provedor 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 logs detalhados ao EclipseLink,
- linha 13: as tabelas serão criadas na instância da camada JPA,
- Adicionar as bibliotecas OpenEJB, EclipseLink e o driver JDBC de MySQL ao arquivo [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 driver JDBC de MySQL
6.2.3. Portação da camada [DAO]
Vamos realizar a migração da camada [DAO] copiando os pacotes do projeto [mv-pam-spring-hibernate] para o projeto [mv-pam-openejb-eclipselink].
- Copiar os pacotes [dao, exception, jpa]
![]() |
Os erros indicados acima ocorrem porque a camada [DAO] copiada utiliza o Spring e as bibliotecas do Spring não fazem mais 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, modificamos a classe [CotisationDao] para transformá-la em EJB:
O import que essa classe gerava no framework Spring desaparece. Gerar um [Clean and Build] do projeto:
![]() |
No [1], não há mais erros na classe [CotisationDao].
6.2.3.2. Os EJB, [EmployeDao] e [IndemniteDao]
Repete-se o mesmo procedimento para os demais elementos da camada [DAO]:
- interfaces IEmployeDaoLocal e IEmployeDaoRemote, derivadas de IEmployeDao
- EJB e EmployeDao, que implementam essas duas interfaces
- interfaces IIndemniteDaoLocal e IIndemniteDaoRemote, derivadas de IIndemniteDao
- EJB e IndemniteDao, que implementam essas duas interfaces
Feito isso, não há mais erros no projeto [2].
6.2.3.3. A classe [PamException]
A classe [PamException] permanece como estava, com uma única diferença:
A linha 5 foi adicionada. Para obter os import corretos, altere para [Fix imports].
Para entender a anotação da linha 5, é preciso lembrar que cada método dos EJB da nossa camada [DAO]:
- é executado em uma transação iniciada e encerrada pelo contêiner EJB
- lança uma exceção do tipo [PamException] assim que algo dá errado
![]() |
Quando a camada [metier] chama um método M da camada [DAO], essa chamada é interceptada pelo contêiner EJB. Tudo ocorre como se houvesse uma classe intermediária entre a camada [metier] e a camada [DAO], aqui denominada [Proxy EJB], interceptando 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 controle para o método M da camada [DAO], que então é executado nessa transação. O método M é concluído com ou sem exceção.
- Se o método M for concluído sem exceção, a execução retorna ao proxy EJB, que encerra a transação validando-a por meio de um commit. O fluxo de execução é então devolvido ao método chamador da camada [metier]
- Se o método M for concluído com uma exceção, a execução retorna ao proxy EJB, que encerra a transação invalidando-a por meio de um rollback. Além disso, ele encapsula essa exceção em um tipo EJBException. O fluxo de execução é então devolvido ao método chamador da camada [metier], que recebe, portanto, um EJBException. A anotação na linha 5 acima impede esse encapsulamento. A camada [metier] receberá, portanto, um PamException. Além disso, o atributo rollback=true indica ao proxy EJB que, ao receber um PamException, ele deve invalidar a transação.
6.2.3.4. Teste da camada [DAO]
Nossa camada [DAO], implementada por EJB, pode ser testada. Começamos copiando o pacote [dao] de [Test Packages] do projeto [mv-pam-springhibernate] para o projeto em desenvolvimento [1]:
![]() |
Manteremos apenas o teste [JUnitInitDB], que inicializa o banco de dados com alguns dados do [2]. Renomearemos a classe [ JUnitInitDbLocal] para [3]. A classe [JUnitInitDBLocal] utilizará a interface local da classe EJB da camada [DAO].
Primeiramente, modificamos a classe [JUnitInitDBLocal] da seguinte maneira:
- 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 contêiner OpenEJB. Essa inicialização é específica e varia a cada contêiner EJB.
- linha 13: temos um contexto JNDI (Java Naming and Directory Interface) que permite acessar os EJB por meio de nomes. Com 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. Lembramos que o arquivo [persistence.xml] foi configurado para recriar as tabelas a cada execução. Antes de executar o teste, é recomendável excluir as eventuais tabelas dos bancos de dados MySQL e [dbpam_eclipselink].
![]() |
- em [1], na guia [Services], exclua as tabelas da conexão do NetBeans estabelecida no parágrafo 6.2.1.
- em [2], o banco de dados [dbpam_eclipselink] não possui mais tabelas
- em [3], o projeto é compilado
- em [4], o teste JUnitInitDBLocal é executado
![]() |
- em [5], o teste foi bem-sucedido
- em [6], atualiza-se a conexã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 contêiner OpenEJB exibiu registros no console:
- 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, desta vez utilizando a interface remota do EJB.
![]() |
No [1], a classe [JUnitInitDBLocal] foi duplicada (copiar/colar) no [JUnitInitDBRemote]. Nessa classe, substituímos as interfaces locais pelas interfaces remotas:
Feito isso, a nova classe de teste pode ser executada. Antes disso, com a conexão do NetBeans [dbpam_eclipselink], exclua as tabelas do banco de dados [dbpam_eclipselink].
![]() |
Com a conexão do NetBeans [dbpam_eclipselink], verifique se o banco de dados foi preenchido.
6.2.4. Portabilidade da camada [metier]
Faremos a migração da camada [metier] copiando os pacotes do projeto [mv-pam-spring-hibernate] para o projeto [mv-pam-openejb-eclipselink].
![]() |
Os erros indicados acima ([1]) decorrem do fato de que a camada [metier] copiada utiliza o Spring e que as bibliotecas do Spring não fazem mais parte do projeto.
6.2.4.1. O EJB [Metier]
Seguimos o mesmo procedimento descrito para o EJB [CotisationDao]. Primeiramente, criamos no [2] as interfaces local e remota do futuro EJB [Metier]. Ambas derivam da interface inicial [IMetier].
Feito isso, no [3], modificamos a classe [Metier] para que ela se torne um EJB:
- linha 1: a anotação @Stateless transforma a classe em EJB
- linha 2: cada método da classe será executado em uma transação
- linha 3: a classe EJB [Metier] implementa as duas interfaces, local e remota, que acabamos de definir
- linha 7: o EJB [Metier] utilizará o EJB [CotisationDao] por meio da interface local deste último. Isso significa que as camadas [metier] e [DAO] devem ser executadas no mesmo JVM.
- linha 6: a anotação @EJB faz com que o contêiner EJB injete ele mesmo a referência na interface local do EJB [CotisationDao]. A outra forma que encontramos é 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]
Nossa camada [metier], implementada por um EJB, pode ser testada. Começamos copiando 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], o primeiro teste é excluído
- em [3], o teste restante é renomeado para [JUnitMetierLocal]
A classe [JUnitMetierLocal] passa a ser a seguinte:
- linha 4: uma referência à interface local do EJB [Metier]
- linhas 8-12: configuração do contêiner 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] fornece o seguinte resultado [1]:
![]() |
No [2], duplicamos o [JUnitMetierLocal] como [JUnitMetierRemote] para testar, desta vez, a interface remota do EJB e do [Metier]. O código do [JUnitMetierRemote] é modificado para utilizar essa interface remota. O restante 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: como, nas interfaces remotas, os objetos trocados entre o cliente e o servidor são passados por valor, é necessário recuperar o resultado retornado pelo método create(Indemnite i). Isso não era obrigatório nas interfaces locais, nas quais os objetos são passados por referência.
Feito isso, o projeto pode ser compilado e o teste [JUnitMetierRemote] executado:
![]() |
6.2.5. Portabilidade da camada [console]
Vamos realizar a portabilidade da camada [console] copiando os pacotes do projeto [mv-pam-spring-hibernate] para o projeto [mv-pam-openejb-eclipselink].
![]() |
Os erros relatados acima ([1]) decorrem do fato de que a camada [metier] copiada utiliza o Spring e que as bibliotecas do Spring não fazem mais parte do projeto. No [2], a classe [Main] é renomeada para [MainLocal]. Ela utilizará a interface local do EJB [Metier].
O código da classe [MainLocal] sofre as seguintes alterações:
As alterações estão nas linhas 13 a 25. Trata-se da forma de fazer referência à camada [metier], que sofreu alterações (linhas 17 a 22). Não explicaremos o novo código, que já foi abordado em exemplos anteriores. Após essas alterações, o projeto não apresenta mais erros (ver [3]).
Configuramos o projeto para que seja executado com os argumentos [1]:
![]() |
Para que o aplicativo de console seja executado normalmente, é necessário que haja dados no banco de dados. Para isso, é preciso modificar o arquivo [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 provedor 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 do banco de dados fossem recriadas a cada execução, foi colocada entre comentários. O projeto deve ser recompilado (Clean and Build) para que essa alteração seja aplicada. Feito isso, é possível executar o programa. Se tudo correr bem, será exibida uma tela de console semelhante à seguinte:
Utilizamos aqui a interface local da camada [metier]. Agora, utilizaremos sua interface remota em uma segunda classe de console:
![]() |
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 foram feitas nas linhas 2 e 8. O projeto [2] foi configurado para executar a classe [MainRemote]. Sua execução produz os mesmos resultados que anteriormente.
6.3. Conclusion
Mostramos 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 o aplicativo inicial havia sido estruturado em camadas. É importante compreender esse ponto.




























