Skip to content

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

  • O arquivo [META-INF/persistence.xml], que configura a camada JPA, passa a ter o seguinte conteúdo:
<?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-openejb-ui-metier-dao-jpa-eclipselinkPU" transaction-type="JTA">
     <!-- entidades JPA -->
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
     <!-- o provedor JPA é EclipseLink -->
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
     <!-- propriedades do provedor -->
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • 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]:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • 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:

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
   // criar uma nova contribuição
  Cotisation create(Cotisation cotisation);
   // editar uma contribuição existente
  Cotisation edit(Cotisation cotisation);
   // excluir uma contribuição existente
  void destroy(Cotisation cotisation);
   // pesquisar uma contribuição específica
  Cotisation find(Long id);
   // obter todos os objetos de contribuição
  List<Cotisation> findAll();

}

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:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}
  • 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:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}
  • 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):

1
2
3
4
5
6
@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
  • 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:
package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
   // obter a folha de pagamento
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // lista de funcionários
  List<Employe> findAllEmployes();
}

Cria-se uma interface local e uma interface remota a partir da interface anterior:

1
2
3
4
5
6
7
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{
}
1
2
3
4
5
6
7
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{
}

A interface EJB da camada [metier] implementa essas duas interfaces:

@Stateless()
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // referência às camadas locais [DAO]
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;
  • 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:

1
2
3
4
5
6
7
8
9
     // configuração do contêiner Open EJB integrado
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // inicialização do contexto JNDI com as propriedades anteriores
    InitialContext initialContext = new InitialContext(properties);
     // instanciação das camadas DAO
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
  • 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:

@EJB
private IemployeDaoLocal employeDaoLocal ;

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]:

1
2
3
4
5
6
7
8
     // configura-se o contêiner Open EJB incorporado
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // inicialização do contexto JNDI do contêiner EJB
    InitialContext initialContext = new InitialContext(properties);

     // instanciação da camada de negócios remota
metier = (IMetierRemote) initialContext.lookup("MetierRemote");

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

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]:
1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<openejb>
  <Resource id="Default JDBC Database">
    JdbcDriver com.mysql.jdbc.Driver
    JdbcUrl jdbc:mysql://localhost:3306/dbpam_eclipselink
    UserName root
  </Resource>
</openejb>
  • 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:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Local;

@Local
public interface ICotisationDaoLocal extends ICotisationDao{
}

Para obter os pacotes corretos import, execute [clic droit sur le code / Fix Imports].

A interface remota ICotisationDaoRemote:

1
2
3
4
5
6
7
package dao;

import javax.ejb.Remote;

@Remote
public interface ICotisationDaoRemote extends ICotisationDao{
}

Em seguida, modificamos a classe [CotisationDao] para transformá-la em EJB:

...
import javax.persistence.PersistenceContext;
import jpa.Cotisation;

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class CotisationDao implements ICotisationDaoLocal, ICotisationDaoRemote {

  @PersistenceContext
  private EntityManager em;
...  

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:

package exception;

import javax.ejb.ApplicationException;

@ApplicationException(rollback=true)
public class PamException extends RuntimeException {

   // código de erro
  private int code;
...

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:

public class JUnitInitDBLocal {

  static private IEmployeDaoLocal employeDao = null;
  static private ICotisationDaoLocal cotisationDao = null;
  static private IIndemniteDaoLocal indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // configurando o contêiner Open EJB incorporado
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // inicialização do contexto JNDI com as propriedades anteriores
    InitialContext initialContext = new InitialContext(properties);
     // instanciação das camadas locais DAO
    employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
}

...
  • 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:

Infos - PersistenceUnit(name=dbpam_eclipselinkPU, provider=org.eclipse.persistence.jpa.PersistenceProvider) - provider time 396ms
Infos - Jndi(name=CotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoLocal) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=CotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao!dao.ICotisationDaoRemote) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/CotisationDao) --> Ejb(deployment-id=CotisationDao)
Infos - Jndi(name=EmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoLocal) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=EmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao!dao.IEmployeDaoRemote) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/EmployeDao) --> Ejb(deployment-id=EmployeDao)
Infos - Jndi(name=IndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoLocal) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=IndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao!dao.IIndemniteDaoRemote) --> Ejb(deployment-id=IndemniteDao)
Infos - Jndi(name=global/classpath.ear/mv-pam-openejb-eclipselink/IndemniteDao) --> Ejb(deployment-id=IndemniteDao)
Infos - existing thread singleton service in SystemInstance() org.apache.openejb.cdi.ThreadSingletonServiceImpl@624a240d
Infos - OpenWebBeans Container is starting...
Infos - Adding OpenWebBeansPlugin : [CdiPlugin]
Infos - All injection points were validated successfully.
Infos - OpenWebBeans Container has started, it took [70] ms.
Infos - Created Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Created Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=EmployeDao, ejb-name=EmployeDao, container=Default Stateless Container)
Infos - Started Ejb(deployment-id=CotisationDao, ejb-name=CotisationDao, container=Default Stateless Container)
Infos - Deployed Application(path=D:\data\istia-1112\netbeans\glassfish\mv-pam\tmp\mv-pam-openejb-eclipselink\classpath.ear)
  • 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:

public class JUnitInitDBRemote {

  static private IEmployeDaoRemote employeDao = null;
  static private ICotisationDaoRemote cotisationDao = null;
  static private IIndemniteDaoRemote indemniteDao = null;

  @BeforeClass
  public static void init() throws Exception {
     // configuração do contêiner Open EJB incorporado
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
      // inicialização do contexto JNDI com as propriedades anteriores
    InitialContext initialContext = new InitialContext(properties);
     // instanciação das camadas remotas DAO
    employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
}

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].

1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Local;

@Local
public interface IMetierLocal extends IMetier{

}
1
2
3
4
5
6
7
8
package metier;

import javax.ejb.Remote;

@Remote
public interface IMetierRemote extends IMetier{

}

Feito isso, no [3], modificamos a classe [Metier] para que ela se torne um EJB:

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class Metier implements IMetierLocal, IMetierRemote {

   // referências à camada local [DAO]
  @EJB
  private ICotisationDaoLocal cotisationDao = null;
  @EJB
  private IEmployeDaoLocal employeDao = null;
  @EJB
  private IIndemniteDaoLocal indemniteDao = null;

   // obter a folha de pagamento
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
          double nbHeuresTravaillées, int nbJoursTravaillés) {
     // recuperação das informações relacionadas ao funcionário
...
  • 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:

public class JUnitMetierLocal {

// camada de negócios local
  static private IMetierLocal metier;

  @BeforeClass
  public static void init() throws NamingException {
     // configuração do contêiner Open EJB incorporado
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // inicialização do contexto JNDI com as propriedades anteriores
    InitialContext initialContext = new InitialContext(properties);

     // instanciação das camadas locais DAO
    IEmployeDaoLocal employeDao = (IEmployeDaoLocal) initialContext.lookup("EmployeDaoLocal");
    ICotisationDaoLocal cotisationDao = (ICotisationDaoLocal) initialContext.lookup("CotisationDaoLocal");
    IIndemniteDaoLocal indemniteDao = (IIndemniteDaoLocal) initialContext.lookup("IndemniteDaoLocal");
     // instanciação da camada de negócios local
    metier = (IMetierLocal) initialContext.lookup("MetierLocal");

     // esvaziamento do banco de dados
...
}
  • 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.

public class JUnitMetierRemote {

   // camada de negócios remota
  static private IMetierRemote metier;

  @BeforeClass
  public static void init() throws NamingException {
     // configuração do contêiner Open EJB incorporado
    Properties properties = new Properties();
    properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
     // inicialização do contexto JNDI com as propriedades anteriores
    InitialContext initialContext = new InitialContext(properties);

     // instanciação das camadas remotas DAO
    IEmployeDaoRemote employeDao = (IEmployeDaoRemote) initialContext.lookup("EmployeDaoRemote");
    ICotisationDaoRemote cotisationDao = (ICotisationDaoRemote) initialContext.lookup("CotisationDaoRemote");
    IIndemniteDaoRemote indemniteDao = (IIndemniteDaoRemote) initialContext.lookup("IndemniteDaoRemote");
     // instanciação da camada de negócios remota
    metier = (IMetierRemote) initialContext.lookup("MetierRemote");

     // esvaziamento do banco de dados
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // preenchimento do banco de dados
    Indemnite indemnite1=new Indemnite(1,1.93,2,3,12);
    Indemnite indemnite2=new Indemnite(2,2.1,2.1,3.1,15);
    indemnite1=indemniteDao.create(indemnite1);
    indemnite2=indemniteDao.create(indemnite2);
    employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
    employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
    cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
  }
}
  • 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:

  public static void main(String[] args) {
     // dados locais
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
...
     // algum erro?
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // Tudo certo — podemos solicitar a folha de pagamento à camada [métier]
    IMetierLocal metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // configuramos o contêiner Open EJB integrado
      Properties properties = new Properties();
      properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");
       // inicialização do contexto JNDI com as propriedades anteriores
      InitialContext initialContext = new InitialContext(properties);
       // instanciação da camada de negócios local
      metier = (IMetierLocal) initialContext.lookup("MetierLocal");
       // cálculo da folha de pagamento
      feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
    } catch (PamException ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
      return;
    } catch (Exception ex) {
      System.err.println("L'erreur suivante s'est produite : " + ex.toString());
      return;
    }
     // exibição detalhada
    String output = "Valeurs saisies :\n";
    output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
....

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:

.......
INFO - Created EJB(deployment-id=IndemniteDao, ejb-name=IndemniteDao, container=Default Stateless Container)
INFO - Deployed Application(path=classpath.ear)
[EL Info]: 2009-09-30 15:09:21.109--ServerSession(16658781)--EclipseLink, version: Eclipse Persistence Services - 1.1.2.v20090612-r4475
[EL Info]: 2009-09-30 15:09:21.937--ServerSession(16658781)--file:/C:/temp/09-09-28/pam-console-metier-dao-openejb-eclipselink-0910/build/classes/-jpa login successful
Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

Informations Employé : 
Nom : Jouveinal
Prénom : Marie
Adresse : 5 rue des oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2

Informations Cotisations : 
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Sécurité sociale : 9.39 %

Informations Indemnités : 
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congés Payés : 15.0 %

Informations Salaire : 
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'entretien : 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

BUILD SUCCESSFUL (total time: 4 seconds)

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]:

// Tudo certo — podemos solicitar a folha de pagamento à camada [metier]
    IMetierRemote metier = null;
    FeuilleSalaire feuilleSalaire = null;
    try {
       // configuramos o contêiner Open EJB integrado
...
       // instanciação da camada de negócios remota
      metier = (IMetierRemote) initialContext.lookup("MetierRemote");
       // cálculo da folha de pagamento
      feuilleSalaire = metier.calculerFeuilleSalaire(args[0], nbHeuresTravaillées, nbJoursTravaillés);
    } catch (PamException ex) {
...
    } catch (Exception ex) {
...
    }

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.