Skip to content

5. Versão 1: Arquitetura Spring / JPA

Propõe-se escrever um aplicativo de console e um aplicativo gráfico que permitam elaborar o holerite das babás empregadas pela “Casa da Primeira Infância” de um município. Esse aplicativo terá a seguinte arquitetura:

5.1. BD O banco de dados

Os dados estáticos necessários para elaborar a folha de pagamento serão armazenados em um banco de dados que, doravante, designaremos como dbpam. Esse banco de dados poderá conter as seguintes tabelas:

Tabela EMPLOYES: reúne informações sobre as diferentes babás

Estrutura:

ID
chave primária
VERSION
número de versão – aumenta a cada modificação da linha
SS
número de previdência social do funcionário – único
NOM
nome do funcionário
prenom
nome próprio
ADRESSE
seu endereço
VILLE
sua cidade
CODEPOSTAL
seu CEP
INDEMNITE_ID
chave estrangeira no campo [ID] da tabela [INDEMNITES]

Seu conteúdo poderia ser o seguinte:

Image

Tabela COTISATIONS: reúne as porcentagens necessárias para o cálculo das contribuições sociais

Estrutura:

ID
chave primária
VERSION
número de versão – aumenta a cada modificação da linha
CSGRDS
porcentagem: contribuição social generalizada + contribuição para o pagamento da dívida social
CSGD
porcentagem: contribuição social generalizada dedutível
SECU
porcentagem: previdência social, pensão de viuvez, aposentadoria
RETRAITE
porcentagem: aposentadoria complementar + seguro-desemprego

Seu conteúdo poderia ser o seguinte:

Image

As alíquotas das contribuições sociais são independentes do empregado. A tabela anterior possui apenas uma linha.

Tabela INDEMNITES: reúne os elementos que permitem o cálculo do salário a ser pago.
ID
chave primária
 
VERSION
número da versão – aumenta a cada modificação da linha
 
INDICE
índice de processamento – único
 
BASEHEURE
preço líquido em euros por hora de plantão
 
ENTRETIENJOUR
subsídio de manutenção em euros por dia de plantão
 
REPASJOUR
Subsídio de alimentação em euros por dia de plantão
 
INDEMNITESCP
Subsídio de férias remuneradas. Trata-se de uma porcentagem a ser aplicada ao salário-base.
 
  

Seu conteúdo poderia ser o seguinte:

Image

Observe-se que os subsídios podem variar de uma babá para outra. Na verdade, eles estão associados a uma babá específica por meio do índice salarial dela. Assim, a Sra. Marie Jouveinal, que possui um índice salarial de 2 (tabela EMPLOYES), tem um salário por hora de 2,1 euros (tabela INDEMNITES).

5.2. Método de cálculo do salário de uma babá

Apresentamos agora o método de cálculo do salário mensal de uma babá. Ele não pretende ser o método utilizado na prática. Tomamos como exemplo o salário da Sra. Marie Jouveinal, que trabalhou 150 horas em 20 dias durante o mês a ser pago.

Os seguintes elementos são levados em consideração:

[TOTALHEURES]: total des heures
travaillées dans le mois

[TOTALJOURS]: total des jours travaillés
dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
O salário-base da babá
é calculado pela seguinte fórmula:
[SALAIREBASE]=([TOTALHEURES]
*[BASEHEURE])*(1+
[INDEMNITESCP]/100)
[SALAIREBASE]=
(150*[2.1])*(1+0.15)= 362,25
Uma determinada quantia de contribuições sociais
devem ser deduzidas desse salário
base:

Contribution sociale généralisée et
contribution au remboursement de la
dette sociale :
 [SALAIREBASE]*[CSGRDS/100]

Contribution sociale généralisée déductible :
 [SALAIREBASE]*[CSGD/100]

Sécurité sociale, veuvage, vieillesse :
 [SALAIREBASE]*[SECU/100]

Retraite Complémentaire + AGPF +
Assurance Chômage :
 [SALAIREBASE]*[RETRAITE/100]
CSGRDS : 12,64
CSGD : 22,28
Sécurité sociale : 34,02
Retraite : 28,55
Total das contribuições sociais:
[COTISATIONSSOCIALES]=
[SALAIREBASE]*(CSGRDS+CSGD
+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
Além disso, a babá tem direito, por cada dia trabalhado, a um auxílio de subsistência e a um auxílio-alimentação. A esse título, ela recebe os seguintes auxílios:

[Indemnités]=[TOTALJOURS]
*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
No final, o salário líquido a ser pago à babá é o seguinte:
[SALAIREBASE]-[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

5.3. Funcionamento do aplicativo de console

Aqui está um exemplo de execução do aplicativo de console em uma janela do DOS:

dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20

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

Vamos escrever um programa que receberá as seguintes informações:

  1. número de previdência social da babá (254104940426058 no exemplo — linha 1)
  2. número total de horas trabalhadas (150 no exemplo — linha 1)
  3. número total de dias trabalhados (20 no exemplo — linha 1)

Observamos que:

  • linhas 9-14: exibem as informações relativas à funcionária cujo número de previdência social foi fornecido
  • linhas 17-20: exibem as alíquotas das diferentes contribuições
  • linhas 23-26: exibem os subsídios associados ao índice salarial do funcionário (aqui, o índice 2)
  • linhas 29-33: exibem os elementos que compõem o salário a ser pago

O aplicativo sinaliza eventuais erros:

Chamada sem parâmetros:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar
Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés

Chamada com dados incorretos:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar  254104940426058 150x 20x
Le nombre d'heures travaillées [150x] est erroné
Le nombre de jours travaillés [20x] est erroné

Chamada com número de previdência social incorreto:


dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar  xx 150 20
L'erreur suivante s'est produite : L'employé de n°[xx] est introuvable

5.4. Funcionamento do aplicativo gráfico

O aplicativo gráfico permite o cálculo dos salários das babás por meio de um formulário Swing:

  • as informações que antes eram passadas como parâmetros para o programa de console agora são inseridas por meio dos campos de entrada [1, 2, 3].
  • O botão [4] solicita o cálculo do salário
  • o formulário exibe os diferentes componentes do salário até o valor líquido a ser pago [5]

A lista suspensa [1, 6] não exibe os números SS dos funcionários, mas sim seus nomes e sobrenomes. Presume-se aqui que não há dois funcionários com o mesmo nome e sobrenome.

5.5. Criação do banco de dados

Executamos o WampServer e utilizamos a ferramenta PhpMyAdmin [1]:

  • no [2], selecionamos a opção [Bases de données],
  • em [3], cria-se um banco de dados [dbpam_hibernate],
  • em [4], o banco de dados criado. Selecionamos-o,
  • em [5], deseja-se importar um script SQL,
  • em [6], usa-se o botão [Parcourir] para selecionar o arquivo,
  • em [7,8], seleciona-se o script SQL,
  • em [9], executa-se o script,
  • em [10], as tabelas foram criadas. Seu conteúdo é o seguinte:

tabela EMPLOYES

Image

tabela INDEMNITES

Image

tabela COTISATIONS

Image

5.6. Implementação JPA

5.6.1. Camada JPA / Hibernate

Vamos configurar a camada JPA no seguinte ambiente:

Um programa de console trabalhará com o banco de dados. Para isso, é necessário:

  • ter um banco de dados,
  • ter o driver JDBC do SGBD, neste caso o MySQL,
  • implementar a camada JPA com o Hibernate,
  • escrever o programa de console.

Criamos o projeto Maven [mv-pam-jpa-hibernate] [1]:

Na arquitetura do nosso aplicativo, precisamos dos seguintes elementos:

  • o banco de dados,
  • o driver JDBC do SGBD MySQL,
  • a camada JPA / Hibernate (entidades e configuração),
  • o programa de console de teste.

5.6.1.1. O banco de dados

Primeiro, vamos criar o banco de dados vazio. Executamos o WampServer e utilizamos a ferramenta PhpMyAdmin [1]:

  • no [2], selecionamos a opção [Bases de données],
  • em [3], cria-se um banco de dados [dbpam_hibernate],
  • em [4], o banco de dados criado.

5.6.1.2. Configuração da camada JPA

A conexão entre a camada JDBC e o banco de dados é feita no arquivo [persistence.xml], que configura a camada JPA. Esse arquivo pode ser criado com o NetBeans:

  • na guia [services] [1], conecta-se ao banco de dados com o driver JDBC de MySQL [2],
  • em [3], o nome do banco de dados ao qual se deseja conectar.
  • em [4], o nome do banco de dados,
  • em [5], conectamo-nos como root sem senha,
  • no [6], é possível testar a conexão,
  • em [7], a conexão foi bem-sucedida.
  • a conexão aparece em [8] e em [9],
  • em [10], adiciona-se um novo elemento ao projeto,
  • em [11], escolhe-se a categoria [Persistence] e, em [12], o elemento [Persistence Unit],
  • em [13], atribui-se um nome a essa unidade de persistência,
  • em [14], escolhe-se uma implementação do Hibernate,
  • em [15], designa-se a conexão que acabamos de criar com o banco de dados MySQL,
  • em [16], indicamos que, ao instanciar a camada JPA, esta deve criar as tabelas correspondentes às entidades JPA do projeto.

Ao final do assistente, é gerado o arquivo [persistence.xml]:

  • o arquivo aparece em um novo ramo do projeto, na pasta [META-INF] [1],
  • que corresponde à pasta [src/main/resources] do projeto [2,3].

Seu conteúdo é o seguinte:


<?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="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
    </properties>
  </persistence-unit>
</persistence>
  • linha 3: o nome da unidade de persistência e o tipo de transações. RESOURCE_LOCAL indica que o próprio projeto gerencia as transações. Aqui, caberá ao programa de console fazer isso,
  • linha 4: a implementação JPA utilizada é o Hibernate,
  • linhas 6-9: as características JDBC da conexão com o banco de dados,
  • linha 11: solicita a criação das tabelas correspondentes às entidades JPA. Na verdade, o NetBeans gera aqui uma configuração incorreta. A configuração deve ser a seguinte:

      <property name="hibernate.hbm2ddl.auto" value="create"/>

Com a opção create**, o Hibernate, ao instanciar a camada JPA, exclui e, em seguida, cria as tabelas correspondentes às entidades JPA. A opção create-drop** faz o mesmo, mas, ao final do ciclo de vida da camada JPA, ela exclui todas as tabelas. Existe outra opção:


      <property name="hibernate.hbm2ddl.auto" value="update"/>

Essa opção cria as tabelas caso elas não existam, mas não as exclui se já existirem.

Adicionaremos mais três propriedades à configuração do Hibernate:


      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>

Elas solicitam que o Hibernate exiba os comandos SQL que ele envia ao banco de dados. O arquivo completo fica, portanto, da seguinte forma:


<?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="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="hibernate.hbm2ddl.auto" value="create"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
      <property name="use_sql_comments" value="true"/>
    </properties>
  </persistence-unit>
</persistence>

5.6.1.3. As dependências

Voltemos à arquitetura do projeto:

Configuramos a camada JPA por meio do arquivo [persistence.xml]. A implementação escolhida foi o Hibernate. Isso gerou dependências no projeto:

  

Essas dependências se devem à inclusão do Hibernate no projeto. Precisamos adicionar outra dependência: o driver JDBC do MySQL, que implementa a camada JDBC da arquitetura. Alteramos o arquivo [pom.xml] da seguinte maneira:


<dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>    
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
    </dependency>
    ...
    <dependency>
      <groupId>org.hibernate.common</groupId>
      <artifactId>hibernate-commons-annotations</artifactId>
      <version>4.0.1.Final</version>
    </dependency>
  </dependencies>

As linhas 8 a 12 adicionam a dependência do driver JDBC do MySQL.

5.6.1.4. As entidades JPA


Pergunta: Seguindo o procedimento do exemplo do parágrafo 4.4, gere as entidades [Cotisation, Indemnite, Employe].


Notas:

  • as entidades farão parte de um pacote denominado [jpa],
  • cada entidade terá um número de versão,
  • se duas entidades estiverem ligadas por uma relação, apenas a relação principal @ManyToOne será criada. A relação inversa @OneToMany não será criada.

5.6.1.5. O código da classe principal

Incluímos no projeto as entidades JPA e [1], desenvolvidas anteriormente:

e, em seguida, adicionamos [2], a seguinte classe [main.Main]:


package main;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {

  public static void main(String[] args) {
    // basta criar o Entity Manager para construir a camada JPA
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpa-hibernatePU");
    EntityManager em=emf.createEntityManager();
    // liberação de recursos
    em.close();
    emf.close();
  }
}
  • linha 10: criamos a EntityManagerFactory da unidade de persistência denominada [mv-pam-jpa-hibernatePU]. Esse nome provém do arquivo [persistence.xml]:

  <persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    ...
  </persistence-unit>
  • linha 12: cria-se o EntityManager. Essa criação gera a camada JPA. O arquivo [persistence.xml] será processado e, portanto, as tabelas do banco de dados serão criadas,
  • linhas 14-15: liberam-se os recursos.

5.6.1.6. Tests

Voltemos à arquitetura do nosso projeto:

Todas as camadas foram implementadas. Executamos o projeto [2].

Os resultados no console são os seguintes:

------------------------------------------------------------------------
Building mv-pam-jpa-hibernate 1.0-SNAPSHOT
------------------------------------------------------------------------

[resources:resources]
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
Copying 1 resource

[compiler:compile]
Nothing to compile - all classes are up to date

[exec:exec]
juin 21, 2012 4:22:47 PM org.hibernate.annotations.common.Version <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
juin 21, 2012 4:22:47 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.1.2}
juin 21, 2012 4:22:47 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
juin 21, 2012 4:22:47 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000402: Using Hibernate built-in connection pool (not for production use!)
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000115: Hibernate connection pool size: 20
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000006: Autocommit mode: true
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000401: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/dbpam_hibernate]
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000046: Connection properties: {user=root, autocommit=true, release_mode=auto}
juin 21, 2012 4:22:48 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
juin 21, 2012 4:22:48 PM org.hibernate.engine.jdbc.internal.LobCreatorBuilder useContextualLobCreation
INFO: HHH000423: Disabling contextual LOB creation as JDBC driver reported JDBC version [3] less than 4
juin 21, 2012 4:22:48 PM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000268: Transaction strategy: org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory
juin 21, 2012 4:22:48 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000227: Running hbm2ddl schema export
Hibernate: 
    alter table EMPLOYES 
        drop 
        foreign key FK75C8D6BC73F24A67
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: HHH000389: Unsuccessful: alter table EMPLOYES drop foreign key FK75C8D6BC73F24A67
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: Table 'dbpam_hibernate.employes' doesn't exist
Hibernate: 
    drop table if exists COTISATIONS
Hibernate: 
    drop table if exists EMPLOYES
Hibernate: 
    drop table if exists INDEMNITES
Hibernate: 
    create table COTISATIONS (
        id bigint not null auto_increment,
        CSGD double precision not null,
        CSGRDS double precision not null,
        RETRAITE double precision not null,
        SECU double precision not null,
        VERSION integer not null,
        primary key (id)
    )
Hibernate: 
    create table EMPLOYES (
        id bigint not null auto_increment,
        SS varchar(15) not null unique,
        ADRESSE varchar(50) not null,
        CP varchar(5) not null,
        NOM varchar(30) not null,
        PRENOM varchar(20) not null,
        VERSION integer not null,
        VILLE varchar(30) not null,
        INDEMNITE_ID bigint not null,
        primary key (id)
    )
Hibernate: 
    create table INDEMNITES (
        id bigint not null auto_increment,
        BASE_HEURE double precision not null,
        ENTRETIEN_JOUR double precision not null,
        INDEMNITES_CP double precision not null,
        INDICE integer not null unique,
        REPAS_JOUR double precision not null,
        VERSION integer not null,
        primary key (id)
    )
Hibernate: 
    alter table EMPLOYES 
        add index FK75C8D6BC73F24A67 (INDEMNITE_ID), 
        add constraint FK75C8D6BC73F24A67 
        foreign key (INDEMNITE_ID) 
        references INDEMNITES (id)
juin 21, 2012 4:22:49 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000230: Schema export complete
juin 21, 2012 4:22:49 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH000030: Cleaning up connection pool [jdbc:mysql://localhost:3306/dbpam_hibernate]
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 2.637s
Finished at: Thu Jun 21 16:22:49 CEST 2012
Final Memory: 8M/153M

Encontram-se na console apenas logs do Hibernate, uma vez que o programa executado não realiza nenhuma outra ação além de instanciar a camada JPA. Observe os seguintes pontos:

  • linha 43: o Hibernate tenta excluir a chave estrangeira da tabela [EMPLOYES],
  • linhas 51-55: exclusão das três tabelas,
  • linha 57: criação da tabela [COTISATIONS],
  • linha 67: criação da tabela [EMPLOYES],
  • linha 80: criação da tabela [INDEMNITES],
  • linha 91: criação da chave estrangeira da tabela [EMPLOYES].

No NetBeans, é possível visualizar as tabelas na conexão criada anteriormente:

As tabelas criadas dependem tanto da implementação da camada JPA utilizada quanto da implementação da camada SGBD utilizada. Assim, uma implementação JPA / EclipseLink com o mesmo banco de dados pode gerar tabelas diferentes. É isso que veremos agora.

Vamos criar um novo projeto Maven no seguinte ambiente:

Seguiremos o procedimento descrito no parágrafo anterior:

  1. criar uma base de dados MySQL [dbpam_eclipselink]. Usaremos o script [dbpam_eclipselink.sql] para gerá-la,
  2. criar o arquivo [persistence.xml] do projeto. Utilizar a implementação JPA 2.0 EclipseLink,
  3. adicionar, nas dependências geradas, a dependência do driver JDBC do MySQL,
  4. adicionar as entidades JPA e o programa de console,
  5. realizar os testes.

O arquivo [persistence.xml] ficará da seguinte forma:


<?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-jpa-eclipselinkPU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="eclipselink.target-database" value="MySQL"/>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="eclipselink.logging.level" value="FINE"/>
      <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>
  • as propriedades 9 a 13 foram geradas pelo assistente do NetBeans,
  • linha 14: essa propriedade nos permite definir o nível de logs do EclipseLink. O nível FINE nos permite saber quais comandos SQL o EclipseLink emitirá no banco de dados,
  • linha 15: ao instanciar a camada JPA / EclipseLink, as tabelas das entidades JPA serão destruídas e, em seguida, criadas.

Os resultados obtidos no console são os seguintes:

------------------------------------------------------------------------
Building mv-pam-jpa-eclipselink 1.0-SNAPSHOT
------------------------------------------------------------------------

[resources:resources]
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
Copying 1 resource

[compiler:compile]
Nothing to compile - all classes are up to date

[exec:exec]
[EL Config]: 2012-06-22 14:35:01.852--ServerSession(730572764)--Thread(Thread[main,5,main])--O tipo de acesso para a classe persistente [class jpa.Cotisation] está definido como [FIELD].
[EL Config]: 2012-06-22 14:35:01.884--ServerSession(730572764)--Thread(Thread[main,5,main])--O tipo de acesso para a classe persistente [class jpa.Employe] está definido como [FIELD].
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--A classe da entidade de destino (referência) para o elemento de mapeamento muitos para um [field indemnite] está sendo definida por padrão como: classe jpa.Indemnite.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--O tipo de acesso para a classe persistente [class jpa.Indemnite] está definido como [FIELD].
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--O nome de alias para a classe de entidade [class jpa.Cotisation] está sendo definido por padrão como: Cotização.
[EL Config]: 2012-06-22 14:35:01.915--ServerSession(730572764)--Thread(Thread[main,5,main])--O nome da coluna para o elemento [id] está sendo definido por padrão como: ID.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--O nome de alias para a classe de entidade [class jpa.Employe] está sendo definido por padrão como: Employe.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--O nome da coluna para o elemento [id] está sendo definido por padrão como: ID.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--O nome de alias para a classe de entidade [class jpa.Indemnite] está sendo definido por padrão como: Indemnite.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--O nome da coluna para o elemento [id] está sendo definido por padrão como: ID.
[EL Config]: 2012-06-22 14:35:01.962--ServerSession(730572764)--Thread(Thread[main,5,main])--O nome da coluna da chave primária para o elemento de mapeamento [field indemnite] está sendo definido por padrão como: ID.
[EL Info]: 2012-06-22 14:35:02.558--ServerSession(730572764)--Thread(Thread[main,5,main])--EclipseLink, versão: Eclipse Persistence Services - 2.3.0.v20110604-r9504
[EL Config]: 2012-06-22 14:35:02.568--ServerSession(730572764)--Conexão(1543921451)--Thread(Thread[main,5,main])--conectando(DatabaseLogin(
    platform=>MySQLPlatform
    user name=> "root"
    datasource URL=> "jdbc:mysql://localhost:3306/dbpam_eclipselink"
))
[EL Config]: 2012-06-22 14:35:02.738--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--Conectado: jdbc:mysql://localhost:3306/dbpam_eclipselink
    User: root@localhost
    Database: MySQL  Version: 5.5.20-log
    Driver: MySQL-AB JDBC Driver  Version: mysql-connector-java-5.1.6 ( Revision: ${svn.Revision} )
[EL Info]: 2012-06-22 14:35:02.798--ServerSession(730572764)--Thread(Thread[main,5,main])--file:/D:/data/istia-1112/netbeans/glassfish/mv-pam/05/mv-pam-jpa-eclipselink/target/classes/_pam-jpa-eclipselinkPU login bem-sucedido
[EL Fine]: 2012-06-22 14:35:02.818--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES DROP FOREIGN KEY FK_EMPLOYES_INDEMNITE_ID
[EL Fine]: 2012-06-22 14:35:03.088--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--DROP TABLE COTISATIONS
[EL Fine]: 2012-06-22 14:35:03.118--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE COTISATIONS (ID BIGINT NOT NULL, CSGD DOUBLE NOT NULL, CSGRDS DOUBLE NOT NULL, RETRAITE DOUBLE NOT NULL, SECU DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.198--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--DROP TABLE EMPLOYES
[EL Fine]: 2012-06-22 14:35:03.238--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE EMPLOYES (ID BIGINT NOT NULL, SS VARCHAR(15) NOT NULL UNIQUE, ADRESSE VARCHAR(50) NOT NULL, CP VARCHAR(5) NOT NULL, NOM VARCHAR(30) NOT NULL, PRENOM VARCHAR(20) NOT NULL, VERSION INTEGER NOT NULL, VILLE VARCHAR(30) NOT NULL, INDEMNITE_ID BIGINT NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.318--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--DROP TABLE INDEMNITES
[EL Fine]: 2012-06-22 14:35:03.338--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE INDEMNITES (ID BIGINT NOT NULL, BASE_HEURE DOUBLE NOT NULL, ENTRETIEN_JOUR DOUBLE NOT NULL, INDEMNITES_CP DOUBLE NOT NULL, INDICE INTEGER NOT NULL UNIQUE, REPAS_JOUR DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.418--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES ADD CONSTRAINT FK_EMPLOYES_INDEMNITE_ID FOREIGN KEY (INDEMNITE_ID) REFERENCES INDEMNITES (ID)
[EL Fine]: 2012-06-22 14:35:03.568--ServerSession(730572764)--Conexão(1296716340)--Tópico(Thread[main,5,main])--CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
[EL Fine]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Tópico(Tópico[main,5,main])--SELECT 1
[EL Warning]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Thread(Thread[main,5,main])--Exceção [EclipseLink-4002] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'sequence' already exists
Error Code: 1050
Call: CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
Query: DataModifyQuery(sql="CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))")
[EL Fine]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--DELETE FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: 2012-06-22 14:35:03.638--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--SELECT * FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: 2012-06-22 14:35:03.638--ServerSession(730572764)--Conexão(1296716340)--Thread(Thread[main,5,main])--INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) valores (SEQ_GEN, 0)
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Conexão (1296716340)--Thread (Thread[main,5,main])--desconexão
[EL Info]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Thread(Thread[main,5,main])--file:/D:/data/istia-1112/netbeans/glassfish/mv-pam/05/mv-pam-jpa-eclipselink/target/classes/_pam-jpa-eclipselinkPU logout bem-sucedido
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Conexão(1543921451)--Thread(Thread[main,5,main])--desconexão
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 3.503s
Finished at: Fri Jun 22 14:35:03 CEST 2012
Final Memory: 8M/153M
  • linhas 26-30: conexão com o banco de dados MySQL,
  • linhas 31-34: confirmação de que a conexão foi bem-sucedida,
  • linha 36: exclusão da chave estrangeira da tabela [EMPLOYES],
  • linha 37: exclusão da tabela [COTISATIONS],
  • linha 38: criação da tabela [COTISATIONS]. É interessante notar que a chave primária ID não possui o atributo MySQL auto_increment. Isso significa que não é MySQL que gera os valores da chave primária,
  • linha 39: exclusão da tabela [EMPLOYES],
  • linha 40: criação da tabela [EMPLOYES]. Sua chave primária ID não possui o atributo MySQL auto_increment,
  • linha 41: exclusão da tabela [INDEMNITES],
  • linha 42: criação da tabela [INDEMNITES]. Sua chave primária ID não possui o atributo MySQL auto_increment,
  • linha 43: criação da chave estrangeira da tabela [EMPLOYES] para a tabela [INDEMNITES],
  • linha 44: criação da tabela [SEQUENCE]. Ela será utilizada para gerar as chaves primárias das três tabelas anteriores,
  • linha 47: ocorre uma exceção, pois essa tabela já existia,
  • linhas 51-53: inicialização da tabela [SEQUENCE].

A existência das tabelas geradas pode ser verificada no NetBeans [1]:

Portanto, a partir das mesmas entidades JPA, as implementações JPA, Hibernate e EclipseLink não geram as mesmas tabelas. No restante do documento, quando a implementação JPA utilizada for:

  • Hibernate, será utilizada a base de dados [dbpam_hibernate];
  • EclipseLink, será utilizado o banco de dados [dbpam_eclipselink].

5.6.3. Tarefa a ser realizada

Seguindo a mesma abordagem da etapa anterior,

  1. crie e teste um projeto [mv-pam-jpa-hibernate-oracle] utilizando uma implementação JPA do Hibernate e uma implementação SGBD do Oracle,
  2. criar e testar um projeto [mv-pam-jpa-hibernate-mssql] utilizando uma implementação JPA do Hibernate e um servidor SGBD SQL,
  3. criar e testar um projeto [mv-pam-jpa-eclipselink-oracle] utilizando uma implementação JPA EclipseLink e um servidor Oracle SGBD,
  4. criar e testar um projeto [mv-pam-jpa-eclipselink-mssql] utilizando uma implementação JPA EclipseLink e um servidor SGBD SQL,

5.6.4. Lazy ou Eager?

Voltemos a uma possível definição da entidade [Employe]:


package jpa;

...

@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
  
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  @Version
  @Column(name="VERSION",nullable=false)
  private int version;
  @Column(name="SS", nullable=false, unique=true, length=15)
  private String SS;
  @Column(name="NOM", nullable=false, length=30)
  private String nom;
  @Column(name="PRENOM", nullable=false, length=20)
  private String prenom;
  @Column(name="ADRESSE", nullable=false, length=50)
  private String adresse;
  @Column(name="VILLE", nullable=false, length=30)
  private String ville;
  @Column(name="CP", nullable=false, length=5)
  private String codePostal;
  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
  private Indemnite indemnite;
  ...
}

As linhas 27 a 29 definem a chave estrangeira da tabela [EMPLOYES] para a tabela [INDEMNITES]. O atributo fetch da linha 27 define a estratégia de pesquisa do campo indemnite da linha 29. Existem dois modos:

  • FetchType.LAZY: quando um funcionário é pesquisado, o valor da indenização correspondente a ele não é retornado. Ele será retornado quando o campo [Employe].indemnite for referenciado pela primeira vez.
  • FetchType.EAGER: quando um funcionário é pesquisado, o valor da indenização correspondente a ele é exibido. Esse é o modo padrão quando nenhum modo é especificado.

Para entender a utilidade da opção FetchType.LAZY, podemos considerar o seguinte exemplo. Uma lista de funcionários sem as indenizações é exibida em uma página da web com um link [Details]. Ao clicar nesse link, são exibidas as indenizações do funcionário selecionado. Observa-se que:

  • para exibir a primeira página, não é necessário incluir os funcionários com seus benefícios. O modo FetchType.LAZY é, portanto, adequado;
  • para exibir a segunda página com os detalhes, é necessário fazer uma consulta adicional ao banco de dados para obter os subsídios do funcionário selecionado.

O modo FetchType.LAZY evita trazer dados em excesso dos quais o aplicativo não precisa imediatamente. Vejamos um exemplo.

O projeto [mv-pam-jpa-hibernate] é duplicado:

  • em [1], o projeto é copiado;
  • em [2], indicamos a pasta da cópia e, em [3], seu nome,
  • em [4], o novo projeto tem o mesmo nome que o antigo. Alteramos isso:
  • em [1], renomeamos o projeto,
  • em [2], renomeamos o projeto e seu artifactId,
  • em [3], o novo projeto.

Modificamos o programa [Main.java] da seguinte maneira:


package main;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import jpa.Employe;

public class Main {

  // a consulta JPQL abaixo retorna um funcionário
  // a chave estrangeira [Employe].indemnite está em FetchType.LAZY
  public static void main(String[] args) {
    // basta criar o Entity Manager para construir a camada JPA
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("pam-jpa-hibernatePU");
    // primeira tentativa
    EntityManager em = emf.createEntityManager();
    Employe employe = (Employe) em.createQuery("select e from Employe e where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
    em.close();
    // exibimos o funcionário
    try {
      System.out.println(employe);
    } catch (Exception ex) {
      System.out.println(ex);
    }
    // segunda tentativa
    em = emf.createEntityManager();
    employe = (Employe) em.createQuery("select e from Employe e left join fetch e.indemnite where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
    // liberar os recursos
    em.close();
    // exibe o funcionário
    try {
      System.out.println(employe);
    } catch (Exception ex) {
      System.out.println(ex);
    }
    // liberação de recursos
    emf.close();
  }
}
  • linha 15: criamos o EntityManagerFactory a partir da camada JPA,
  • linha 17: obtemos o EntityManager, que nos permite interagir com a camada JPA,
  • linha 18: solicita-se o funcionário com o nome Jouveinal,
  • linha 19: fecha-se o EntityManager. Isso tem como efeito fechar o contexto de persistência.
  • linha 22: exibimos o funcionário recebido.

A classe [Employe] é a seguinte:


package jpa;

...

@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
  
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  @Version
  @Column(name="VERSION",nullable=false)
  private int version;
  @Column(name="SS", nullable=false, unique=true, length=15)
  private String SS;
  @Column(name="NOM", nullable=false, length=30)
  private String nom;
  @Column(name="PRENOM", nullable=false, length=20)
  private String prenom;
  @Column(name="ADRESSE", nullable=false, length=50)
  private String adresse;
  @Column(name="VILLE", nullable=false, length=30)
  private String ville;
  @Column(name="CP", nullable=false, length=5)
  private String codePostal;
  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
  private Indemnite indemnite;
  
  
  /**
   * Returns a string representation of the object.  This implementation constructs
   * that representation based on the id fields.
   * @return a string representation of the object.
   */
  @Override
  public String toString() {
    return "jpa.Employe[id=" + getId()
    + ",version="+getVersion()
    +",SS="+getSS()
    + ",nom="+getNom()
    + ",prenom="+getPrenom()
    + ",adresse="+getAdresse()
    +",ville="+getVille()
    +",code postal="+getCodePostal()
    +",indice="+getIndemnite().getIndice()
    +"]";
  }
  ...
}
  • linha 27: o campo indemnite é revertido para o modo LAZY,
  • linha 47: utiliza o campo indemnite. Se o método toString for chamado enquanto o campo indemnite ainda não tiver sido revertido, ele será revertido nesse momento. A menos que o contexto de persistência tenha sido fechado, como no exemplo.

Voltemos ao código do [Main]:

  • linhas 21-25: deveria ocorrer uma exceção. De fato, o método toString será chamado. Ele utilizará o campo indemnite. Esse campo será procurado. Como o contexto de persistência foi fechado, a entidade [Employe] recuperada não existe mais, daí a exceção.
  • linha 27: cria-se um novo EntityManager,
  • linha 28: solicita-se o funcionário Jouveinal, indicando explicitamente na consulta JPQL o valor da indenização correspondente. Essa solicitação explícita é necessária porque o modo de pesquisa dessa indenização é LAZY,
  • linha 30: encerra-se o EntityManager,
  • linhas 32-36: exibe-se novamente o funcionário. Não deve haver nenhuma exceção.

Para executar o projeto, é necessário um banco de dados preenchido. Ele será criado seguindo os passos descritos no parágrafo 5.5. Além disso, o arquivo [persistence.xml] deve ser modificado:


<?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="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>jpa.Cotisation</class>
    <class>jpa.Employe</class>
    <class>jpa.Indemnite</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
    </properties>
  </persistence-unit>
</persistence>
  • retiramos a opção que criava as tabelas. O banco de dados aqui já existe e está preenchido,
  • e removemos as opções que faziam com que o Hibernate registrasse os comandos SQL que ele enviava para o banco de dados.

A execução do projeto gera as duas exibições a seguir no console:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
jpa.Employe[id=31,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
  • linha 1: a exceção que ocorreu ao tentar buscar a indenização que faltava, já que a sessão estava encerrada. Percebe-se que a indenização não foi recuperada devido ao modo LAZY,
  • linha 2: o funcionário com seu adicional obtido por meio de uma consulta que contornou o modo LAZY.

5.6.5. Tarefa a ser realizada

Seguindo um procedimento semelhante ao que acabou de ser seguido, crie um projeto [mv-pam-pa-eclipselink-lazy] que mostre o comportamento do EclipseLink em comparação com o modo LAZY.

Obtêm-se os seguintes resultados:

jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]

No modo LAZY, ambas as consultas retornaram o valor da indenização junto com o funcionário. Ao pesquisar na internet sobre essa anomalia, descobre-se que a anotação [FetchType.LAZY] (linha 1):


  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;

não é uma ordem, mas uma sugestão. O implementador JPA não é obrigado a segui-la. Vemos, portanto, que o código às vezes passa a depender da implementação JPA utilizada. É possível definir, por meio de configuração, o comportamento esperado para o modo LAZY no EclipseLink.

5.6.6. Para a continuação

A arquitetura do aplicativo a ser construído é a seguinte:

Para o restante do documento, duplicaremos o projeto Maven [mv-pam-jpa-hibernate] nos projetos [mv-pam-spring-hibernate] e [1, 2, 3]:

  • e, em seguida, renomearemos o novo projeto para [4, 5, 6].

Alteraremos as dependências do novo projeto. O arquivo [pom.xml] passa a ter o seguinte conteúdo:


<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-spring-hibernate</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-pam-spring-hibernate</name>
  <url>http://maven.apache.org</url>
  <repositories>
    <repository>
      <url>http://repo1.maven.org/maven2/</url>
      <id>swing-layout</id>
      <layout>default</layout>
      <name>Repository for library Library[swing-layout]</name>
    </repository>
  </repositories>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>4.1.2</version>
      <type>jar</type>
    </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>
</project>
  • linhas 25-31: a dependência para os testes JUnit,
  • linhas 32-41: as dependências para o pool de conexões do Apache DBCP,
  • linhas 42-65: as dependências para o framework Spring,
  • linhas 67-71: as dependências para a implementação JPA / Hibernate,
  • linhas 72-76: a dependência do driver JDBC do MySQL,
  • linhas 77-81: a dependência da interface Swing. Ela é adicionada automaticamente pelo NetBeans quando se adiciona uma interface Swing ao projeto.

Além disso, serão geradas as duas bases MySQL:

  • [dbpam_hibernate] a partir do script [dbpam_hibernate.sql],
  • [dbpam_eclipselink] a partir do script [dbpam_eclipselink.sql],

5.7. , interfaces das camadas [metier] e [DAO]

Voltemos à arquitetura do aplicativo:

Na arquitetura acima, qual interface a camada [DAO] deve oferecer à camada [metier] e qual interface a camada [metier] deve oferecer à camada [ui]? Uma primeira abordagem para definir as interfaces das diferentes camadas é examinar os diferentes casos de uso (use cases) do aplicativo. Aqui, temos dois, dependendo da interface de usuário escolhida: console ou formulário gráfico.

Vamos examinar o modo de uso da aplicação em console:

dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20

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

Informations Cotisations :
CSGRDS : 3.49 %
...

Informations Indemnités :
...

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

A aplicação recebe três informações do usuário (ver linha 1 acima)

  • o número do plano de saúde da babá
  • o número de horas trabalhadas no mês
  • o número de dias trabalhados no mês

A partir dessas informações e de outras registradas em arquivos de configuração, o aplicativo exibe as seguintes informações:

  • linhas 4-6: os valores inseridos
  • linhas 8-10: as informações relacionadas ao funcionário cujo número de previdência social foi fornecido
  • linhas 12-14: as alíquotas das diferentes contribuições sociais
  • linhas 16-17: os diversos valores pagos à babá
  • linhas 19-24: os elementos da folha de pagamento da babá

A camada [metier] deve fornecer algumas informações à camada [ui]:

  1. as informações relacionadas a uma babá identificada por seu número de previdência social. Essas informações se encontram na tabela [EMPLOYES]. Isso permite exibir as linhas 6-8.
  2. os valores das diversas alíquotas de contribuições sociais a serem deduzidas do salário bruto. Essas informações estão na tabela [COTISATIONS]. Isso permite exibir as linhas 10 a 12.
  3. os valores das diversas indenizações relacionadas à função de babá. Essas informações estão na tabela [INDEMNITES]. Isso permite exibir as linhas 14-15.
  4. os componentes do salário exibidos nas linhas 18 a 22.

A partir disso, seria possível definir um primeiro lançamento da interface [IMetier], apresentada pela camada [metier] à camada [ui]:

1
2
3
4
5
6
package metier;

public interface IMetier {
   // obter a folha de pagamento
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
}
  • linha 1: os elementos da camada [metier] são inseridos no pacote [metier]
  • linha 5: o método [ calculerFeuilleSalaire ] recebe como parâmetros as três informações obtidas pela camada [ui] e retorna um objeto do tipo [FeuilleSalaire] contendo as informações que a camada [ui] exibirá no console. A classe [FeuilleSalaire] poderia ser a seguinte:
package metier;

import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;

public class FeuilleSalaire {
   // campos privados
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

  ...
}
  • linha 9: o funcionário ao qual se refere a folha de pagamento — informação nº 1 exibida pela camada [ui]
  • linha 10: as diferentes alíquotas de contribuição — informação nº 2 exibida pela camada [ui]
  • linha 11: os diversos subsídios vinculados ao índice do funcionário — informação nº 3 exibida pela camada [ui]
  • linha 12: os elementos que compõem o salário do funcionário — informação n.º 4 exibida pela camada [ui]

Um segundo caso de uso da camada [métier] surge com a interface gráfica:

Vê-se acima que a lista suspensa [1, 2] apresenta todos os funcionários. Essa lista deve ser solicitada à camada [métier]. A interface , que utiliza essa camada, passa então a ter a seguinte aparência:

package metier;

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

public interface IMetier {
   // obter a folha de pagamento
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // lista de funcionários
  public List<Employe> findAllEmployes();
}
  • linha [10]: o método que permitirá que a camada [ui] solicite a lista de todos os funcionários à camada [métier].

A camada [metier] só pode inicializar os campos [Employe, Cotisation, Indemnite] do objeto [FeuilleSalaire] acima consultando a camada [DAO], pois essas informações estão nas tabelas do banco de dados. O mesmo se aplica à obtenção da lista de todos os funcionários. É possível criar uma única interface [DAO] que gerencie o acesso às três entidades [Employe, Cotisation, Indemnite]. No entanto, decidimos aqui criar uma interface [DAO] para cada entidade.

A interface [DAO] para o acesso às entidades [Cotisation] da tabela [COTISATIONS] será a seguinte:

package dao;

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

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

}
  • na linha 6, a interface [ICotisationDao] gerencia o acesso à entidade [Cotisation] e, portanto, à tabela [COTISATIONS] do banco de dados. Nosso aplicativo precisa apenas do método [findAll] da linha 16, que permite recuperar todo o conteúdo da tabela [COTISATIONS]. Aqui, quisemos abordar um caso mais geral, em que todas as operações CRUD (Create, Read, Update, Delete) são realizadas na entidade.
  • linha 8: o método [create] cria uma nova entidade [Cotisation]
  • linha 10: o método [edit] modifica uma entidade [Cotisation] existente
  • linha 12: o método [destroy] exclui uma entidade [Cotisation] existente
  • linha 14: o método [find] permite recuperar uma entidade [Cotisation] existente por meio de seu identificador id
  • linha 16: o método [findAll] retorna em uma lista todas as entidades [Cotisation] existentes

Vamos nos deter na assinatura do método [create]:

       // criar uma nova contribuição
Cotisation create(Cotisation cotisation);

O método create possui um parâmetro cotisation do tipo Cotisation. O parâmetro cotisation deve ser persistido, c.a.d. aqui inserido na tabela [COTISATIONS]. Antes dessa persistência, o parâmetro cotisation possui um identificador id sem valor. Após a persistência, o campo id possui um valor que é a chave primária do registro adicionado à tabela [COTISATIONS]. O parâmetro cotisation é, portanto, um parâmetro de entrada/saída do método create. Não parece necessário que o método create retorne também o parâmetro cotisation como resultado. Como o método chamador possui uma referência ao objeto [Cotisation cotisation], caso este seja modificado, ele terá acesso ao objeto modificado, uma vez que possui uma referência a ele. Portanto, ela pode saber o valor que o método create atribuiu ao campo id do objeto [Cotisation cotisation]. A assinatura do método poderia, portanto, ser mais simples:

       // criar uma nova contribuição
void create(Cotisation cotisation);

Ao escrever uma interface, é importante lembrar que ela pode ser utilizada em dois contextos diferentes: local e distant. No contexto local, o método chamador e o método chamado são executados no mesmo JVM:

Se a camada [metier] chamar o método create da camada [DAO], ela possui, de fato, uma referência no parâmetro [Cotisation cotisation] que é passada para o método.

No contexto distant, o método chamador e o método chamado são executados em JVM diferentes:

No exemplo acima, a camada [metier] é executada na JVM 1 e a camada [DAO] na JVM 2, em duas máquinas diferentes. As duas camadas não se comunicam diretamente. Entre elas, intercala-se uma camada que chamaremos de camada de comunicação [1]. Esta é composta por uma camada de transmissão [2] e uma camada de recepção [3]. O desenvolvedor geralmente não precisa escrever essas camadas de comunicação. Elas são geradas automaticamente por ferramentas de software. A camada [metier] é escrita como se fosse executada na mesma JVM que a camada [DAO]. Portanto, não há nenhuma modificação no código.

O mecanismo de comunicação entre a camada [metier] e a camada [DAO] é o seguinte:

  • a camada [metier] chama o método create da camada [DAO], passando-lhe o parâmetro [Cotisation cotisation1]
  • esse parâmetro é, na verdade, passado para a camada de emissão [2]. Esta transmitirá pela rede o valor do parâmetro cotisation1 e não sua referência. A forma exata desse valor depende do protocolo de comunicação utilizado.
  • A camada de recepção [3] recuperará esse valor e, a partir dele, reconstruirá um objeto [Cotisation cotisation2], que é uma réplica do parâmetro inicial enviado pela camada [metier]. Agora temos dois objetos idênticos (em termos de conteúdo) em duas camadas JVM diferentes: cotisation1 e cotisation2.
  • A camada de recepção passará o objeto cotisation2 para o método create da camada [DAO], que o armazenará no banco de dados. Após essa operação, o campo id do objeto cotisation2 foi inicializado com a chave primária do registro adicionado à tabela [COTISATIONS]. Esse não é o caso do objeto cotisation1, ao qual a camada [metier] faz referência. Se quisermos que a camada [metier] tenha uma referência ao objeto cotisation2, é preciso enviá-la a ela. Por isso, é necessário alterar a assinatura do método create da camada [DAO]:
       // criar uma nova contribuição
Cotisation create(Cotisation cotisation);
  • Com essa nova assinatura, o método create retornará como resultado o objeto persistido cotisation2. Esse resultado é retornado à camada de recepção [3], que havia chamado a camada [DAO]. Esta, por sua vez, retornará o valor (e não a referência) de cotisation2 à camada de emissão [2].
  • A camada de emissão [2] recuperará esse valor e, a partir dele, reconstruirá um objeto [Cotisation cotisation3], que é uma representação do resultado retornado pelo método create da camada [DAO].
  • O objeto [Cotisation cotisation3] é devolvido ao método da camada [metier], cuja chamada ao método create da camada [DAO] havia iniciado todo esse mecanismo. A camada [metier] pode, portanto, conhecer o valor da chave primária atribuído ao objeto [Cotisation cotisation1], cuja persistência ela havia solicitado: trata-se do valor do campo id de cotisation3.

A arquitetura anterior não é a mais comum. É mais frequente encontrar as camadas [metier] e [DAO] na mesma JVM:

Nessa arquitetura, são os métodos da camada [metier] que devem retornar resultados, e não os da camada [DAO]. No entanto, a seguinte assinatura do método create da camada [DAO]:

       // criar uma nova contribuição
Cotisation create(Cotisation cotisation);

permite que não façamos suposições sobre a arquitetura efetivamente implementada. Utilizar assinaturas que funcionem independentemente da arquitetura escolhida, seja ela local ou remota, implica que, no caso de um método chamado modificar alguns de seus parâmetros:

  • esses parâmetros também devem fazer parte do resultado do método chamado
  • o método chamador deve utilizar o resultado do método chamado e não as referências dos parâmetros modificados que ele transmitiu ao método chamado.

Assim, mantemos a possibilidade de migrar de uma arquitetura locale para uma arquitetura distante sem alteração de código. Reexaminemos, sob essa perspectiva, a interface [ICotisationDao]:

package dao;

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

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

}
  • linha 8: o caso do método create foi tratado
  • linha 10: o método edit utiliza seu parâmetro [Cotisation cotisation1] para atualizar o registro da tabela [COTISATIONS] que possui a mesma chave primária que o objeto cotisation. Ele retorna como resultado o objeto cotisation2, que representa o registro modificado. O parâmetro cotisation1, por sua vez, não é alterado. O método deve retornar cotisation2 como resultado, independentemente de se estar no contexto de uma arquitetura distante ou locale.
  • linha 12: o método destroy exclui o registro da tabela [COTISATIONS] que possui a mesma chave primária que o objeto cotisation passado como parâmetro. Este não é alterado. Portanto, não precisa ser retornado.
  • linha 14: o parâmetro id do método find não é alterado pelo método. Ele não precisa fazer parte do resultado.
  • linha 16: o método findAll não possui parâmetros. Portanto, não é necessário analisá-lo.

Por fim, apenas a assinatura do método create deve ser adaptada para ser utilizável no contexto de uma arquitetura distante. Os raciocínios anteriores serão válidos para as demais interfaces [DAO]. Não vamos repeti-los e utilizaremos diretamente assinaturas que podem ser usadas tanto no contexto de uma arquitetura distante quanto no de uma locale.

A interface [DAO] para o acesso às entidades [Indemnite] da tabela [INDEMNITES] será a seguinte:

package dao;

import java.util.List;
import jpa.Indemnite;

public interface IIndemniteDao {
     // criar uma entidade Indenização
  public Indemnite create(Indemnite indemnite);
     // modificar uma entidade “Indenização”
  public Indemnite edit(Indemnite indemnite);
     // excluir uma entidade Indenização
  public void destroy(Indemnite indemnite);
     // pesquisar uma entidade Indenização por meio de seu identificador
  public Indemnite find(Long id);
     // obter todas as entidades Indemnite
  public List<Indemnite> findAll();

}
  • Na linha 6, a interface [IIndemniteDao] gerencia os acessos à entidade [Indemnite] e, portanto, à tabela [INDEMNITES] do banco de dados. Nosso aplicativo precisa apenas do método [findAll] da linha 16, que permite recuperar todo o conteúdo da tabela [INDEMNITES]. Aqui, quisemos abordar um caso mais geral, em que todas as operações CRUD (Create, Read, Update, Delete) são realizadas na entidade.
  • linha 8: o método [create] cria uma nova entidade [Indemnite]
  • linha 10: o método [edit] modifica uma entidade [Indemnite] existente
  • linha 12: o método [destroy] exclui uma entidade [Indemnite] existente
  • linha 14: o método [find] permite localizar uma entidade [Indemnite] existente por meio de seu identificador id
  • linha 16: o método [findAll] retorna em uma lista todas as entidades [Indemnite] existentes

A interface [DAO] para acessar as entidades [Employe] da tabela [EMPLOYES] será a seguinte:

package dao;

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

public interface IEmployeDao {
     // criar uma nova entidade “Empregado”
  public Employe create(Employe employe);
     // alterar uma entidade “Empregado” existente
  public Employe edit(Employe employe);
     // excluir uma entidade “Empregado”
  public void destroy(Employe employe);
     // pesquisar uma entidade “Empregado” por meio de seu identificador id
  public Employe find(Long id);
     // pesquisar uma entidade “Empregado” por meio de seu n.º SS
  public Employe find(String SS);
     // obter todas as entidades “Empregado”
  public List<Employe> findAll();
}
  • na linha 6, a interface [IEmployeDao] gerencia o acesso à entidade [Employe] e, portanto, à tabela [EMPLOYES] do banco de dados. Nosso aplicativo precisa apenas do método [findAll] da linha 16, que permite recuperar todo o conteúdo da tabela [EMPLOYES]. Aqui, quisemos abordar um caso mais geral, em que todas as operações CRUD (Create, Read, Update, Delete) são realizadas na entidade.
  • linha 8: o método [create] cria uma nova entidade [Employe]
  • linha 10: o método [edit] modifica uma entidade [Employe] existente
  • linha 12: o método [destroy] exclui uma entidade [Employe] existente
  • linha 14: o método [find] permite localizar uma entidade [Employe] existente por meio de seu identificador id
  • linha 16: o método [find(String SS)] permite recuperar uma entidade [Employe] existente por meio de seu número SS. Vimos que esse método era necessário para o aplicativo de console.
  • linha 18: o método [findAll] retorna, em uma lista, todas as entidades [Employe] existentes. Vimos que esse método era necessário para o aplicativo gráfico.

5.8. A classe [PamException]

A camada [DAO] trabalhará com a classe API e JDBC do Java. Essa API lança exceções controladas do tipo [SQLException], que apresentam duas desvantagens:

  • elas tornam o código mais pesado, pois este deve, obrigatoriamente, lidar com essas exceções por meio de try/catch.
  • elas devem ser declaradas na assinatura dos métodos da interface [IDao] por meio de um “throws SQLException”. Isso impede que essa interface seja implementada por classes que lançariam uma exceção controlada de um tipo diferente de [SQLException].

Para resolver esse problema, a camada [DAO] “repassará” apenas exceções não controladas do tipo [PamException].

  • a camada [JDBC] lança exceções do tipo [SQLException]
  • a camada [JPA] lança exceções específicas da implementação JPA utilizada
  • a camada [DAO] lança exceções do tipo [PamException] não controladas

Isso tem duas consequências:

  • a camada [metier] não terá a obrigação de tratar as exceções da camada [DAO] com try/catch. Ela poderá simplesmente deixá-las subir até a camada [ui].
  • os métodos da interface [IDao] não precisam incluir na sua assinatura a natureza da exceção [PamException], o que permite implementar essa interface com classes que lançariam outro tipo de exceção não controlada.

A classe [PamException] será colocada no pacote [exception] do projeto NetBeans:

Seu código é o seguinte:

package exception;

@SuppressWarnings("serial")
public class PamException extends RuntimeException {

   // código de erro
  private int code;

  public PamException(int code) {
    super();
    this.code = code;
  }

  public PamException(String message, int code) {
    super(message);
    this.code = code;
  }

  public PamException(Throwable cause, int code) {
    super(cause);
    this.code = code;
  }

  public PamException(String message, Throwable cause, int code) {
    super(message, cause);
    this.code = code;
  }

   // getter e setter

  public int getCode() {
    return code;
  }

  public void setCode(int code) {
    this.code = code;
  }

}
  • linha 4: [PamException] deriva de [RuntimeException]. Trata-se, portanto, de um tipo de exceção que o compilador não nos obriga a tratar por meio de um `try/catch` nem a incluir na assinatura dos métodos. É por esse motivo que [PamException] não consta na assinatura dos métodos da interface [IDao]. Isso permite que essa interface seja implementada por uma classe que lance outro tipo de exceção, desde que essa classe também derive de [RuntimeException].
  • Para diferenciar os erros que podem ocorrer, utiliza-se o código de erro da linha 7. Os três construtores das linhas 14, 19 e 24 são os da classe pai [RuntimeException], aos quais foi adicionado um parâmetro: o código de erro que se deseja atribuir à exceção.

O funcionamento do aplicativo, do ponto de vista das exceções, será o seguinte:

  • a camada [DAO] encapsulará qualquer exceção encontrada em uma exceção do tipo [PamException] e a reenviará para a camada [métier].
  • A camada [métier] permitirá que as exceções lançadas pela camada [DAO] sejam repassadas. Ela encapsulará todas as exceções que ocorrerem na camada [métier] em uma exceção do tipo [PamException] e a reenviará para a camada [ui].
  • A camada [ui] intercepta todas as exceções que são repassadas pelas camadas [métier] e [DAO]. Ela se limitará a exibir a exceção no console ou na interface gráfica.

Vamos agora examinar, sucessivamente, a implementação das camadas [DAO] e [metier].

5.9. A camada [DAO] do aplicativo [PAM]

Estamos no contexto da seguinte arquitetura:

5.9.1. Implementação

Leituras recomendadas: parágrafo 3.1.3 de [ref1]


Questão: Utilizando a integração Spring / JPA, escreva as classes [CotisationDao, IndemniteDao, EmployeDao] para implementar as interfaces [ICotisationDao, IIndemniteDao, IEmployeDao]. Cada método de classe interceptará uma eventual exceção e a encapsulará em uma exceção do tipo [PamException] com um código de erro específico para a exceção interceptada.


As classes de implementação farão parte do pacote [dao]:

  

5.9.2. Configuração

Leituras recomendadas: parágrafo 3.1.5 de [ref1]

A integração DAO / JPA é configurada pelo arquivo Spring [spring-config-dao.xml] e pelos arquivos JPA e [persistence.xml]:


Pergunta: escreva o conteúdo desses dois arquivos. Suponha-se que o banco de dados utilizado seja o MySQL5 [dbpam_hibernate], gerado pelo script SQL [dbpam_hibernate.sql]. O arquivo Spring definirá os três beans a seguir: employeDao do tipo EmployeDao, indemniteDao do tipo IndemniteDao, cotisationDao do tipo CotisationDao. Além disso, a implementação JPA utilizada será Hibernate.


5.9.3. Testes

Leituras recomendadas: parágrafos 3.1.6 e 3.1.7 de [ref1]

Agora que a camada [DAO] está escrita e configurada, podemos testá-la. A arquitetura dos testes será a seguinte:

5.9.4. InitDB

Vamos criar dois programas de teste para a camada [DAO]. Esses programas serão colocados no pacote [dao] [2] do ramo [Test Packages] [1] do projeto NetBeans. Esse ramo não está incluído no projeto gerado pela opção [Build project], o que garante que os programas de teste que colocarmos nele não serão incluídos no arquivo .jar final do projeto.

As classes colocadas no ramo [Test Packages] têm acesso às classes presentes no ramo [Source Packages], bem como às bibliotecas de classes do projeto. Se os testes precisarem de bibliotecas diferentes das do projeto, estas devem ser declaradas no ramo [Test Libraries] [2].

As classes de teste utilizam a ferramenta de testes unitários JUnit:

  • [JUnitInitDB] não realiza nenhum teste. Ela preenche o banco de dados com alguns registros e, em seguida, os exibe no console.
  • [JUnitDao] realiza uma série de testes e verifica os resultados.

A estrutura da classe [JUnitInitDB] é a seguinte:

package dao;

...

public class JUnitInitDB {

  private IEmployeDao employeDao = null;
  private ICotisationDao cotisationDao = null;
  private IIndemniteDao indemniteDao = null;

  @BeforeClass
  public void init(){
     // configuração do aplicativo
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
     // camadas DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
  }

  @Test
  public void initDB(){
     // preenchimento do banco de dados
...
     // exibindo o conteúdo do banco de dados
...
  }

  @Before()
  public void clean(){
     // esvaziar o banco de dados
...
  }
}
  • o método [init] é executado antes do início da série de testes (anotação @BeforeClass). Ele instancia a camada [DAO].
  • O método [clean] é executado antes de cada teste (anotação @Before). Ele esvazia o banco de dados.
  • O método [initDB] é um teste (anotação @Test). É o único. Um teste deve conter instruções de verificação Assert.assertCondition. Aqui, não haverá nenhuma. O método é, portanto, um falso teste. Sua função é preencher o banco de dados com algumas linhas e, em seguida, exibir o conteúdo do banco de dados no console. São utilizados aqui os métodos create e findAll das camadas [DAO].

Questão: complete o código da classe [JUnitInitDB]. Use como referência o exemplo do parágrafo 3.1.6 de [ref1]. O código irá gerar o conteúdo apresentado no parágrafo 5.1.


5.9.5. Implementaçã e dos testes

Agora estamos prontos para executar o [InitDB]. Descrevemos o procedimento com o SGBD e o MySQL5:

  • as classes [1], os arquivos de configuração [2] e as classes de teste da camada [DAO] e [3] estão configuradas,
  • o projeto é compilado [4]
  • a classe [JUnitInitDB] é executada [5]. O SGBD MySQL5 é iniciado com uma base [dbpam_hibernate] existente,
  • a janela [Test Results] e [6] indicam que os testes foram bem-sucedidos. Essa mensagem não é relevante neste caso, pois o programa [JUnitInitDB] não contém nenhuma instrução de asserção Assert.assertCondition que pudesse causar a falha do teste. No entanto, isso demonstra que não houve nenhuma exceção durante a execução do teste.

A janela [Output] contém os logs da execução, os do Spring e os do próprio teste. As exibições feitas pela classe [JUnitInitDB] são as seguintes:

------------- Standard Output ---------------
Employés ----------------------
jpa.Employe[id=5,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
jpa.Employe[id=6,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La brûlerie,ville=St Marcel,code postal=49014,indice=1]
Indemnités ----------------------
jpa.Indemnite[id=5,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0]
jpa.Indemnite[id=6,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités CP=15.0]
Cotisations ----------------------
jpa.Cotisation[id=3,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88]
------------- ---------------- ---------------

As tabelas [EMPLOYES, INDEMNITES, COTISATIONS] foram preenchidas. Isso pode ser verificado por meio de uma conexão do NetBeans ao banco de dados [dbpam_hibernate].

  • em [1], na aba [services], visualizam-se os dados da tabela [employes] da conexão [dbpam_hibernate] [2],
  • em [3], o resultado.

5.9.6. JUnitDao

Agora, vamos nos concentrar em uma segunda classe de testes, [JUnitDao]:

A estrutura da classe será a seguinte:

package dao;

import exception.PamException;
...

public class JUnitDao {

// camadas DAO
  static private IEmployeDao employeDao;
  static private IIndemniteDao indemniteDao;
  static private ICotisationDao cotisationDao;

  @BeforeClass
  public static void init() {
     // log
    log("init");
     // configuração do aplicativo
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
     // camadas DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
  }

  @AfterClass
  public static void terminate() {
  }

  @Before()
  public void clean() {
...
  }

   // registros
  private static void log(String message) {
    System.out.println("----------- " + message);
  }

   // testes
  @Test
  public void test01() {
    log("test01");
     // lista de contribuições
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // adicionar uma contribuição
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // solicitação
    cotisation = cotisationDao.find(cotisation.getId());
     // verificação
    Assert.assertNotNull(cotisation);
    Assert.assertEquals(3.49, cotisation.getCsgrds(), 1e-6);
    Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
    Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
    Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
     // estamos modificando
    cotisation.setCsgrds(-1);
    cotisation.setCsgd(-1);
    cotisation.setRetraite(-1);
    cotisation.setSecu(-1);
    Cotisation cotisation2 = cotisationDao.edit(cotisation);
     // verificações
    Assert.assertEquals(cotisation.getVersion() + 1, cotisation2.getVersion());
    Assert.assertEquals(-1, cotisation2.getCsgrds(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getCsgd(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getRetraite(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getSecu(), 1e-6);
     // solicita-se o elemento alterado
    Cotisation cotisation3 = cotisationDao.find(cotisation2.getId());
     // verificações
    Assert.assertEquals(cotisation3.getVersion(), cotisation2.getVersion());
    Assert.assertEquals(-1, cotisation3.getCsgrds(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getCsgd(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getRetraite(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getSecu(), 1e-6);
     // o elemento é excluído
    cotisationDao.destroy(cotisation3);
     // verificações
    Cotisation cotisation4 = cotisationDao.find(cotisation3.getId());
    Assert.assertNull(cotisation4);
    cotisations = cotisationDao.findAll();
    Assert.assertEquals(nbCotisations, cotisations.size());
  }


  @Test
  public void test02(){
    log("test02");
     // solicita-se a lista de indenizações
...
     // adiciona-se uma indenização
..
     // busca-se a indenização no banco de dados – recupera-se indenização1
..
     // verifica-se se indenização1 = indenização
...
     // modifica-se a indenização obtida e salva-se a modificação em BD. Obtém-se a indenização2
 ...
     // verifica-se a versão de indenização2
    ...
     // busca-se indemnite2 no banco de dados – obtém-se indemnite3
    ...
     // verifica-se se indenização3 = indenização2
    ...
     // elimina-se do banco de dados a imagem de indemnite3
    ...
     // buscamos indemnite3 no banco de dados
    ...
     // verifica-se se foi obtida uma referência nula
 ...
  }

  @Test
  public void test03(){
    log("test03");
     // repete-se um teste semelhante aos anteriores para o Empregado
 ...
  }

  @Test
  public void test04(){
    log("test04");
     // testa-se o método [IEmployeDao].find(String SS)
     // primeiro com um funcionário existente
     // e, em seguida, com um funcionário inexistente
...
  }

  @Test
  public void test05(){
    log("test05");
     // criamos duas indenizações com o mesmo índice
     // viola a restrição de exclusividade do índice
     // verifica-se se ocorre uma exceção do tipo PamException
     // e que ela tenha o número de erro esperado
...
  }

  @Test
  public void test06(){
    log("test06");
     // criam-se dois funcionários com o mesmo número SS
     //, o que viola a restrição de exclusividade do número SS
     // verifica-se se ocorre uma exceção do tipo PamException
     // e que ela apresenta o número de erro esperado
...

  }

  @Test
  public void test07(){
    log("test07");
     // criam-se dois funcionários com o mesmo número SS, o primeiro com a função “create” e o segundo com a função “edit”
     // viola a restrição de exclusividade do número SS
     // verifica-se se ocorre uma exceção do tipo PamException
     // e que ela possui o número de erro esperado
...
  }

  @Test
  public void test08(){
    log("test08");
     // excluir um funcionário que não existe não gera exceção
     // ele é adicionado e depois excluído – verifica-se isso
...
  }

  @Test
  public void test09(){
    log("test09");
     // alterar um funcionário sem ter a versão correta deve causar uma exceção
     // verificamos isso
...
  }

  @Test
  public void test10(){
    log("test10");
     // excluir um funcionário sem ter a versão correta deve gerar uma exceção
     // verificamos isso
...

  }

   // getters e setters
  ...
}

Na classe de testes anterior, o banco de dados é esvaziado antes de cada teste.


Pergunta: escreva os seguintes métodos:

1 - test02: inspire-se no test01

2 - test03: um funcionário possui um campo do tipo Indemnite. Portanto, é necessário criar uma entidade Indemnite e uma entidade Employe

3 - test04.


Seguindo o mesmo procedimento utilizado para a classe de testes [JUnitInitDB], obtêm-se os seguintes resultados:

  • em [1], executa-se a classe de testes
  • em [2], os resultados dos testes na janela [Test Results]

Vamos provocar um erro para ver como ele é sinalizado na página de resultados:

  @Test
  public void test01() {
    log("test01");
     // lista de contribuições
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // adicionamos uma contribuição
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // solicitação
    cotisation = cotisationDao.find(cotisation.getId());
     // verificação
    Assert.assertNotNull(cotisation);
    Assert.assertEquals(0, cotisation.getCsgrds(), 1e-6);
    Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
    Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
    Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
     // alteração
....
}

Linha 13: a asserção causará um erro, já que o valor de Csgrds é 3,49 (linha 8). A execução da classe de testes apresenta os seguintes resultados:

  • a página de resultados [1] agora mostra que houve testes que falharam.
  • Em [2], um resumo da exceção que fez com que o teste falhasse. Nele consta o número da linha do código Java onde a exceção ocorreu.

5.10. A camada [metier] do aplicativo [PAM]

Agora que a camada [DAO] foi escrita, passamos ao estudo da camada de negócios [2]:

5.10.1. A interface Java [IMetier]

Esta foi descrita no parágrafo 5.7. Apresentamos um resumo abaixo:

package metier;

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

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

A implementação da camada [metier] será feita em um pacote [metier]:

 

O pacote [metier] incluirá, além da interface [IMetier] e sua implementação [Metier], duas outras classes: [FeuilleSalaire] e [ElementsSalaire]. A classe [FeuilleSalaire] foi brevemente apresentada no parágrafo 5.7. Voltaremos a ela agora.

5.10.2. A classe [FeuilleSalaire]

O método [calculerFeuilleSalaire] da interface [IMetier] retorna um objeto do tipo [FeuilleSalaire] que representa os diferentes elementos de uma folha de pagamento. Sua definição é a seguinte:

package metier;

import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;

public class FeuilleSalaire implements Serializable{
   // campos privados
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

   // construtores
  public FeuilleSalaire() {

  }

  public FeuilleSalaire(Employe employe, Cotisation cotisation, ElementsSalaire elementsSalaire) {
    setEmploye(employe);
    setCotisation(cotisation);
    setElementsSalaire(elementsSalaire);
  }

   // toString
  public String toString() {
    return "[" + employe + "," + cotisation + "," + elementsSalaire + "]";
  }

   // acessórios
...  
}
  • linha 7: a classe implementa a interface Serializable porque suas instâncias podem ser trocadas pela rede.
  • linha 9: o funcionário ao qual se refere a folha de pagamento
  • linha 10: as diferentes alíquotas de contribuição
  • linha 11: os diversos subsídios vinculados ao índice do funcionário
  • linha 12: os componentes de seu salário
  • linhas 14-22: os dois construtores da classe
  • linhas 25-27: método [toString] que identifica um objeto [FeuilleSalaire] específico
  • linhas 29 e seguintes: os acessores públicos aos campos privados da classe

A classe [ElementsSalaire], referenciada na linha 11 da classe [FeuilleSalaire] acima, reúne os elementos que constituem um holerite. Sua definição é a seguinte:

package metier;

public class ElementsSalaire implements Serializable{

   // campos privados
  private double salaireBase;
  private double cotisationsSociales;
  private double indemnitesEntretien;
  private double indemnitesRepas;
  private double salaireNet;

   // fabricantes
  public ElementsSalaire() {

  }

  public ElementsSalaire(double salaireBase, double cotisationsSociales,
    double indemnitesEntretien, double indemnitesRepas,
    double salaireNet) {
    setSalaireBase(salaireBase);
    setCotisationsSociales(cotisationsSociales);
    setIndemnitesEntretien(indemnitesEntretien);
    setIndemnitesRepas(indemnitesRepas);
  }

   // toString
  public String toString() {
    return "[salaire base=" + salaireBase + ",cotisations sociales=" + cotisationsSociales + ",indemnités d'entretien="
      + indemnitesEntretien + ",indemnités de repas=" + indemnitesRepas + ",salaire net="
      + salaireNet + "]";
  }

   // acessores públicos
...  
}
  • linha 3: a classe implementa a interface Serializable porque é um componente da classe FeuilleSalaire, que deve ser serializável.
  • linha 6: o salário-base
  • linha 7: as contribuições sociais pagas sobre esse salário-base
  • linha 8: os auxílios diários para manutenção do filho
  • linha 9: os auxílios diários para alimentação da criança
  • linha 10: o salário líquido a ser pago à babá
  • linhas 12-24: os construtores da classe
  • linhas 27-31: método [toString] que identifica um objeto específico [ElementsSalaire]
  • linhas 34 e seguintes: os acessadores públicos aos campos privados da classe

5.10.3. A classe de implementação [Metier] da camada [metier]

A classe de implementação [Metier] da camada [metier] poderia ser a seguinte:

package metier;

...

@Transactional
public class Metier implements IMetier {

   // referência na camada [DAO]
  private ICotisationDao cotisationDao = null;
  private IEmployeDao employeDao=null;


   // obter a folha de pagamento
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
...
  }

   // lista de funcionários
   public List<Employe> findAllEmployes() {
     ...
  }

   // getters e setters
...
 }
  • linha 5: a anotação Spring @Transactional faz com que cada método da classe seja executado dentro de uma transação.
  • linhas 9-10: as referências nas camadas [DAO] às entidades [Cotisation, Employe, Indemnite]
  • linhas 14-17: o método [calculerFeuilleSalaire]
  • linhas 20-22: o método [findAllEmployes]
  • linha 24 e seguintes: os acessadores públicos dos campos privados da classe

Questão: escreva o código do método [findAllEmployes].



Questão: escreva o código do método [calculerFeuilleSalaire].


Observe os seguintes pontos:

  • o modo de cálculo do salário foi explicado no parágrafo 5.2.
  • se o parâmetro [SS] não corresponder a nenhum funcionário (a camada [DAO] retornou um ponteiro null), o método lançará uma exceção do tipo [PamException] com um código de erro apropriado.

5.10.4. Testes da camada [metier]

Criamos dois programas de teste:

As classes de teste [3] são criadas no pacote [metier] [2] do ramo [Test Packages] [1] do projeto.

A classe [JUnitMetier_1] poderia ser a seguinte:

package metier;

...

public class JUnitMetier_1 {

// camada de negócios
  private IMetier metier;

  @BeforeClass
  public void init(){
     // log
    log("init");
     // configuração do aplicativo
     // instanciação da camada [metier]
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
    metier = (IMetier) ctx.getBean("metier");
     // camadas DAO
    IEmployeDao employeDao=(IEmployeDao) ctx.getBean("employeDao");
    ICotisationDao cotisationDao=(ICotisationDao) ctx.getBean("cotisationDao");
    IIndemniteDao indemniteDao=(IIndemniteDao) ctx.getBean("indemniteDao");
     // 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
    Indemnite indemnite1=indemniteDao.create(new Indemnite(1,1.93,2,3,12));
    Indemnite indemnite2=indemniteDao.create(new Indemnite(2,2.1,2.1,3.1,15));
    Employe employe2=employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
    Employe employe1=employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
    Cotisation cotisation1=cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
  }

   // logs
  private void log(String message) {
    System.out.println("----------- " + message);
  }

   // teste
  @Test
  public void test01(){
     // cálculo das folhas de pagamento
    System.out.println(metier.calculerFeuilleSalaire("260124402111742",30, 5));
    System.out.println(metier.calculerFeuilleSalaire("254104940426058",150, 20));
    try {
      System.out.println(metier.calculerFeuilleSalaire("xx", 150, 20));
    } catch (PamException ex) {
      System.err.println(String.format("PamException[Code=%d, message=%s]",ex.getCode(), ex.getMessage()));
    }
  }
}

Não há nenhuma asserção Assert.assertCondition na classe. O objetivo é simplesmente calcular alguns salários para, em seguida, verificá-los manualmente. A exibição na tela obtida pela execução da classe anterior é a seguinte:

1
2
3
4
5
6
7
Testsuite: metier.JUnitMetier_1
----------- init
....
[jpa.Employe[id=22,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La brûlerie,ville=St Marcel,code postal=49014,indice=1],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88],jpa.Indemnite[id=29,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0],[salaire base=64.85,cotisations sociales=17.45,indemnités d'entretien=10.0,indemnités de repas=15.0,salaire net=72.4]]
[jpa.Employe[id=21,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88],jpa.Indemnite[id=30,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités CP=15.0],[salaire base=362.25,cotisations sociales=97.48,indemnités d'entretien=42.0,indemnités de repas=62.0,salaire net=368.77]]
PamException[Code=101, message=L'employé de n°[xx] est introuvable]
Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3,234 sec
  • linha 4: a folha de pagamento de Justine Laverti
  • linha 5: a folha de pagamento de Marie Jouveinal
  • linha 6: a exceção devido ao fato de que o funcionário com o nº SS 'xx' não existe.

Pergunta: a linha 17 de [JUnitMetier_1] utiliza o bean Spring denominado metier. Indique a definição desse bean no arquivo [spring-config-metier-dao.xml].


A classe [JUnitMetier_2] poderia ser a seguinte:

package metier;

...
public class JUnitMetier_2 {

// camada de negócios
  private IMetier metier;

  @BeforeClass
  public void init(){
...
  }

   // registros
  private void log(String message) {
    System.out.println("----------- " + message);
  }

   // teste
  @Test
  public void test01(){
...
  }
}

A classe [JUnitMetier_2] é uma cópia da classe [JUnitMetier_1], na qual, desta vez, foram inseridas asserções no método test01.


Tarefa: escreva o método test01.


Ao executar a classe [JUnitMetier_2], obtêm-se os seguintes resultados, se tudo correr bem:

Image

5.11. A camada [ui] do aplicativo [PAM] – versão console

Agora que a camada [metier] foi gravada, resta-nos gravar a camada [ui] [1]:

Criaremos duas implementações diferentes da camada [ui]: uma versão console e uma versão gráfica swing:

5.11.1. A classe [ui.console.Main]

Primeiramente, vamos nos concentrar no aplicativo de console implementado pela classe [ui.console.Main] acima. Seu funcionamento foi descrito no parágrafo 5.3. A estrutura básica da classe [Main] poderia ser a seguinte:

package ui.console;

import exception.PamException;
import metier.FeuilleSalaire;
import metier.IMetier;

import java.util.ArrayList;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  /**
   * @param args
   */
  public static void main(String[] args) {
     // dados locais
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
     // verificação do número de parâmetros
...
     // lista de erros
    ArrayList erreurs = new ArrayList();
     // o segundo parâmetro deve ser um número real >0
...
     // erro?
    if (...) {
      erreurs.add("Le nombre d'heures travaillées [" + args[1]
        + "] est erroné");
    }
     // o terceiro parâmetro deve ser um número inteiro maior que 0
...
     // erro?
    if (...) {
      erreurs.add("Le nombre de jours travaillés [" + args[2]
        + "] est erroné");
    }
     // erros?
    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
    FeuilleSalaire feuilleSalaire = null;
    try {
       // instanciação da camada [metier]
      ...
       // cálculo da folha de pagamento
      ...
    } 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]);
    output += ajouteInfo("Nombre d'heures travaillées", args[1]);
    output += ajouteInfo("Nombre de jours travaillés", args[2]);
    output += ajouteInfo("\nInformations Employé", "");
    output += ajouteInfo("Nom", feuilleSalaire.getEmploye().getNom());
    output += ajouteInfo("Prénom", feuilleSalaire.getEmploye().getPrenom());
    output += ajouteInfo("Adresse", feuilleSalaire.getEmploye().getAdresse());
    output += ajouteInfo("Ville", feuilleSalaire.getEmploye().getVille());
    output += ajouteInfo("Code Postal", feuilleSalaire.getEmploye().getCodePostal());
    output += ajouteInfo("Indice", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndice());
    output += ajouteInfo("\nInformations Cotisations", "");
    output += ajouteInfo("CSGRDS", ""+ feuilleSalaire.getCotisation().getCsgrds() + " %");
    output += ajouteInfo("CSGD", ""+ feuilleSalaire.getCotisation().getCsgd() + " %");
    output += ajouteInfo("Retraite", ""+ feuilleSalaire.getCotisation().getRetraite() + " %");
    output += ajouteInfo("Sécurité sociale", ""+ feuilleSalaire.getCotisation().getSecu() + " %");
    output += ajouteInfo("\nInformations Indemnités", "");
    output += ajouteInfo("Salaire horaire", ""+ feuilleSalaire.getEmploye().getIndemnite().getBaseHeure() + " euro");
    output += ajouteInfo("Entretien/jour", ""+ feuilleSalaire.getEmploye().getIndemnite().getEntretienJour() + " euro");
    output += ajouteInfo("Repas/jour", ""+ feuilleSalaire.getEmploye().getIndemnite().getRepasJour() + " euro");
    output += ajouteInfo("Congés Payés", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndemnitesCP()+ " %");
    output += ajouteInfo("\nInformations Salaire", "");
    output += ajouteInfo("Salaire de base", ""+ feuilleSalaire.getElementsSalaire().getSalaireBase()+ " euro");
    output += ajouteInfo("Cotisations sociales", ""+ feuilleSalaire.getElementsSalaire().getCotisationsSociales()+ " euro");
    output += ajouteInfo("Indemnités d'entretien", ""+ feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ " euro");
    output += ajouteInfo("Indemnités de repas", ""+ feuilleSalaire.getElementsSalaire().getIndemnitesRepas()+ " euro");
    output += ajouteInfo("Salaire net", ""+ feuilleSalaire.getElementsSalaire().getSalaireNet() + " euro");

    System.out.println(output);
  }

  static String ajouteInfo(String message, String valeur) {
    return message + " : " + valeur + "\n";
  }
}

Questão: complete o código acima.


5.11.2. Execução

Para executar a classe [ui.console.Main], proceda da seguinte maneira:

  • em [1], selecione as propriedades do projeto,
  • em [2], selecione a propriedade [Run] do projeto,
  • utilize o botão [3] para indicar a classe (conhecida como classe principal) a ser executada,
  • selecione a classe [4],
  • a classe aparece em [5]. Ela precisa de três argumentos para ser executada (n.º SS, número de horas trabalhadas, número de dias trabalhados). Esses argumentos são inseridos em [6],
  • feito isso, é possível executar o projeto [7]. A configuração anterior faz com que seja a classe [ui.console.Main] que será executada.

Os resultados da execução são exibidos na janela [output]:

5.12. A camada [ui] do aplicativo [PAM] – versão gráfica

Agora implementamos a camada [ui] com uma interface gráfica:

  • em [1], a classe [PamJFrame] da interface gráfica
  • em [2]: a interface gráfica

5.12.1. Um tutorial rápido

Para criar a interface gráfica, pode-se proceder da seguinte maneira:

  • [1]: cria-se um novo arquivo com o botão [1] [New File...]
  • [2]: seleciona-se a categoria do arquivo [Swing GUI Forms], c.a.d. formulários gráficos
  • [3]: selecione o tipo [JFrame Form], um tipo de formulário vazio
  • [5]: atribui-se um nome ao formulário, que também será uma classe
  • [6]: insere-se o formulário em um pacote
  • [8]: o formulário é adicionado à árvore de estrutura do projeto
  • [9]: o formulário pode ser acessado por meio de duas perspectivas: [Design] e [9], que permitem desenhar os diferentes componentes do formulário, e [Source] e [10 ci-dessous], que dão acesso ao código Java do formulário. No fim das contas, um formulário é uma classe Java como qualquer outra. A perspectiva [Design] facilita o desenho do formulário. A cada adição de um componente no modo [Design], código Java é adicionado na perspectiva [Source] para incorporá-lo.
  • [11]: a lista de componentes Swing disponíveis para um formulário pode ser encontrada na janela [Palette].
  • [12]: a janela [Inspector] apresenta a árvore de componentes do formulário. Os componentes com representação visual estarão no ramo [JFrame], e os demais, no ramo [Other Components].
  • no [13], selecionamos um componente [JLabel] com um simples clique
  • em [14], o colocamos no formulário no modo [Design]
  • em [15], definimos as propriedades do JLabel (texto, fonte).
  • em [16], o resultado obtido.
  • em [17], solicitamos a visualização prévia do formulário
  • em [18], o resultado
  • em [19], o rótulo [JLabel1] foi adicionado à árvore de componentes na janela [Inspector]
  • em [20] e [21]: na perspectiva [Source] do formulário, foi adicionado código Java para gerenciar o JLabel adicionado.

Um tutorial sobre a criação de formulários com o NetBeans está disponível no endereço [http://www.netbeans.org/kb/trails/matisse.html].

5.12.2. A interface gráfica [PamJFrame]

Vamos criar a seguinte interface gráfica:

  • em [1], a interface gráfica
  • em [2], a árvore de seus componentes: um JLabel e seis contêineres JPanel

JLabel1

JPanel1

JPanel2

JPanel3

JPanel4

JPanel5


Exercício prático: construir a interface gráfica anterior com a ajuda do tutorial [http://www.netbeans.org/kb/trails/matisse.html].


5.12.3. Os eventos da interface gráfica

Leituras recomendadas: capítulo [Interfaces graphiques] de [ref2].

Vamos tratar o clique no botão [jButtonSalaire]. Para criar o método de tratamento desse evento, podemos proceder da seguinte forma:

O manipulador do clique no botão [JButtonSalaire] é gerado:

1
2
3
    private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {
       // TODO insira seu código de processamento aqui:
}

O código Java que associa o método anterior ao clique no botão [JButtonSalaire] também é gerado:

1
2
3
4
5
6
    jButtonSalaire.setText("Salaire");
    jButtonSalaire.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButtonSalaireActionPerformed(evt);
      }
});

São as linhas 2 a 5 que indicam que o clique (eventualmente do tipo ActionPerformed) no botão [jButtonSalaire] (linha 2) deve ser gerenciado pelo método [jButtonSalaireActionPerformed] (linha 4).

Também trataremos do evento [caretUpdate] (deslocamento do cursor de entrada) no campo de entrada [jTextFieldHT]. Para criar o manipulador desse evento, procedemos da mesma forma que anteriormente:

O manipulador do evento [caretUpdate] no campo de entrada [jTextFieldHT] é gerado:

  private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {                                         
 ...
  }

O código Java que associa o método anterior ao evento [caretUpdate] no campo de entrada [jTextFieldHT] também é gerado:

1
2
3
4
5
    jTextFieldHT.addCaretListener(new javax.swing.event.CaretListener() {
      public void caretUpdate(javax.swing.event.CaretEvent evt) {
        jTextFieldHTCaretUpdate(evt);
      }
});

As linhas 1 a 4 indicam que o evento [caretUpdate] (linha 2) no botão [jTextFieldHT] (linha 1) deve ser tratado pelo método [ jTextFieldHTCaretUpdate] (linha 3).

5.12.4. Inicialização da interface gráfica

Voltemos à arquitetura do nosso aplicativo:

A camada [ui] precisa de uma referência à camada [metier]. Vamos relembrar como essa referência foi obtida na aplicação console:

1
2
3
    // instanciação da camada [metier]
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
IMetier metier = (IMetier) ctx.getBean("metier");

O método é o mesmo na aplicação gráfica. É necessário que, quando esta for inicializada, a referência [IMetier metier] da linha 3 acima também seja inicializada. O código gerado para a interface gráfica é, por enquanto, o seguinte:

package ui.swing;

...
public class PamJFrame extends javax.swing.JFrame {

   /** Cria um novo formulário PamJFrame */
  public PamJFrame() {
    initComponents();
  }

   /** Este método é chamado de dentro do construtor para
   * initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is
   * always regenerated by the Form Editor.
   */
   // <editor-fold defaultstate="collapsed" desc="Código gerado">
  private void initComponents() {
...
  }// </editor-fold>

  private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {                                         
 ...
  }                                        

  private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {                                               
...
  }                                              

  public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new PamJFrame().setVisible(true);
      }
    });
  }

   // Declaração de variáveis — não modifique
  private javax.swing.JButton jButtonSalaire;
...
   // Fim da declaração de variáveis

}
  • linhas 29-35: o método estático [main] que inicia o aplicativo
  • linha 32: uma instância da interface gráfica [PamJFrame] é criada e tornada visível.
  • linhas 7-9: o construtor da interface gráfica.
  • linha 8: chamada ao método [initComponents] definido na linha 17. Esse método é gerado automaticamente a partir do trabalho realizado no modo [Design]. Não se deve alterá-lo.
  • linha 21: o método que irá gerenciar o deslocamento do cursor de entrada no campo [jTextFieldHT]
  • linha 25: o método que irá gerenciar o clique no botão [jButtonSalaire]

Para adicionar nossas próprias inicializações ao código anterior, podemos proceder da seguinte forma:

  /** Cria um novo formulário PamJFrame */
  public PamJFrame() {
    initComponents();
    doMyInit();
  }

...

   // variáveis de instância
  private IMetier metier=null;
  private List<Employe> employes=null;
  private String[] employesCombo=null;
  private double heuresTravaillées=0;

   // inicializações específicas
  public void doMyInit(){
     // inicialização do contexto
    try{
       // instanciação da camada [metier]
...
     // lista de funcionários
...
    }catch (PamException ex){
     // a mensagem da exceção é inserida em [jTextAreaStatus]
...
     // retorno
      return;
    }
     // botão “salário” desativado
...
     // jScrollPane1 oculto
...
     // spinner de dias trabalhados
    jSpinnerJT.setModel(new SpinnerNumberModel(0,0,31,1));
     // caixa de seleção de funcionários
    employesCombo=new String[employes.size()];
    int i=0;
    for(Employe employe : employes){
      employesCombo[i++]=employe.getPrenom()+" "+employe.getNom();
    }
    jComboBoxEmployes.setModel(new DefaultComboBoxModel(employesCombo));
}
  • linha 4: chamamos um método proprietário para realizar nossas próprias inicializações. Estas são definidas pelo código das linhas 10 a 42

Pergunta: com a ajuda dos comentários, complete o código da procedimento [doMyInit].


5.12.5. Manipuladores de eventos


Questão: escreva o método [jTextFieldHTCaretUpdate]. Esse método deve garantir que, se o dado presente no campo [jTextFieldHT] não for um número real >=0, o botão [jButtonSalaire] fique desativado.



Questão: escreva o método [jButtonSalaireActionPerformed], que deve exibir a folha de pagamento do funcionário selecionado em [jComboBoxEmployes].


5.12.6. Execução da interface gráfica

Para executar a interface gráfica, modificaremos a configuração [Run] do projeto:

  • para [1], definindo a classe da interface gráfica

O projeto deve estar completo com seus arquivos de configuração (persistence.xml, spring-config-metier-dao.xml) e a classe da interface gráfica. Inicie o alvo SGBD antes de executar o projeto.

Estamos interessados na seguinte arquitetura, na qual a camada JPA agora é implementada por EclipseLink:

5.13.1. O projeto NetBeans

O novo projeto do NetBeans é obtido por meio da cópia do projeto anterior:

  • em [1]: após clicar com o botão direito do mouse no projeto Hibernate, selecione Copy
  • usando o botão [2], selecione a pasta pai do novo projeto. O nome da pasta aparece em [3].
  • em [4], nomeie o novo projeto
  • em [5], o nome da pasta do projeto
  • em [1], o novo projeto foi criado. Ele tem o mesmo nome que o original,
  • em [2] e [3], renomeia-se como [mv-pam-spring-eclipselink].

O projeto deve ser alterado em dois pontos para se adaptar à nova camada JPA / EclipseLink:

  1. no [4], os arquivos de configuração do Spring devem ser alterados. Neles está, de fato, a configuração da camada JPA.
  2. no [5], as bibliotecas do projeto devem ser modificadas: as do Hibernate devem ser substituídas pelas do EclipseLink.

Vamos começar por este último ponto. O arquivo [pom.xml] para o novo projeto será este:


<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-spring-eclipselink</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mv-pam-spring-eclipselink</name>
  <url>http://maven.apache.org</url>
  <repositories>
    <repository>
      <url>http://repo1.maven.org/maven2/</url>
      <id>swing-layout</id>
      <layout>default</layout>
      <name>Repository for library Library[swing-layout]</name>
    </repository>
    <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>
  
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>commons-dbcp</groupId>
      <artifactId>commons-dbcp</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>commons-pool</groupId>
      <artifactId>commons-pool</artifactId>
      <version>1.6</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.1.1.RELEASE</version>
      <type>jar</type>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>3.1.1.RELEASE</version>
      <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>
</project>
  • linhas 73-82: as dependências para a implementação JPA EclipseLink,
  • linhas 19-24: o repositório Maven para EclipseLink.

Os arquivos de configuração do Spring devem ser alterados para indicar que a implementação JPA foi alterada. Nos dois arquivos, apenas a seção que configura a camada JPA é alterada. Por exemplo, em [spring-config-metier-dao.xml], temos:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

   <!-- camadas de aplicação -->
   <!- - DAO -->
  <bean id="employeDao" class="dao.EmployeDao" />
  <bean id="indemniteDao" class="dao.IndemniteDao" />
  <bean id="cotisationDao" class="dao.CotisationDao" />
   <!-- área de negócios -->
  <bean id="metier" class="metier.Metier">
    <property name="employeDao" ref="employeDao"/>
    <property name="indemniteDao" ref="indemniteDao"/>
    <property name="cotisationDao" ref="cotisationDao"/>  
  </bean>

   <!-- configuração JPA -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <!--
          <property name="showSql" value="true" />
    -->
        <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
        <property name="generateDdl" value="true" />
   <!--
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
    <property name="loadTimeWeaver">
      <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
  </bean>

   <!-- fonte de dados DBCP -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/dbpam_hibernate" />
    <property name="username" value="root" />
<!--
    <property name="password" value="" />
-->
  </bean>
....  
</beans>

As linhas 19 a 36 configuram a camada JPA. A implementação JPA utilizada é o Hibernate (linha 22). Além disso, o banco de dados de destino é [dbpam_hibernate] (linha 41).

Para mudar para uma implementação JPA / EclipseLink, as linhas 19 a 35 acima são substituídas pelas linhas a seguir:

   <!-- configuração JPA -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
        <!--
          <property name="showSql" value="true" />
  -->
        <property name="databasePlatform" value="org.eclipse.persistence.platform.database.MySQLPlatform" />
        <!--
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
    <property name="loadTimeWeaver">
      <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
</bean>
  • linha 5: a implementação JPA utilizada é EclipseLink
  • linha 9: a propriedade databasePlatform define o SGBD como destino, neste caso, MySQL
  • linha 11: para gerar as tabelas do banco de dados quando a camada JPA é instanciada. Aqui, a propriedade está comentada.
  • linha 7: para visualizar no console os comandos SQL emitidos pela camada JPA. Aqui, a propriedade está comentada.

Além disso, o banco de dados de destino passa a ser [dbpam_eclipselink] (linha 4 abaixo):

1
2
3
4
5
6
7
8
9
<!-- a fonte de dados DBCP -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink" />
    <property name="username" value="root" />
<!--
    <property name="password" value="" />
-->
  </bean>

5.13.2. Execução dos testes

Antes de testar a aplicação inteira, é recomendável verificar se os testes JUnit são aprovados com a nova implementação JPA. Antes de executá-los, começaremos por excluir as tabelas do banco de dados. Para isso, na guia [Runtime] do NetBeans, se necessário, criaremos uma conexão com o banco de dados dbpam_eclipselink / MySQL5. Uma vez conectado ao banco de dados dbpam_eclipselink / MySQL5, será possível prosseguir com a exclusão das tabelas, conforme mostrado abaixo:

  • [1]: antes da exclusão
  • [2]: após a exclusão

Feito isso, é possível executar o primeiro teste na camada [DAO]: InitDB, que preenche o banco de dados. Para que as tabelas excluídas anteriormente sejam recriadas pelo aplicativo, é preciso garantir que, na configuração JPA / EclipseLink do Spring, a linha:

        <property name="generateDdl" value="true" />

exista e não esteja comentada.

Compilamos o projeto (Build) e, em seguida, executamos o teste [JUnitInitDB] :

  • em [1], o teste InitDB é executado.
  • No [2], ele falha. A exceção é lançada pelo Spring e não por um teste que teria falhado.

Causado por: org.springframework.beans.factory.BeanCreationException: Erro ao criar o bean com o nome 'entityManagerFactory' definido no recurso do caminho de classe [spring-config-DAO.xml]: Falha na invocação do método init; a exceção aninhada é java.lang.IllegalStateException: É necessário iniciar o agente Java para usar InstrumentationLoadTimeWeaver. Consulte a documentação do Spring.

O Spring indica que há um problema de configuração. A mensagem não é clara. O motivo da exceção foi explicado no parágrafo 3.1.9 de [ref1]. Para que a configuração do Spring / EclipseLink funcione, o JVM que executa a aplicação deve ser iniciado com um parâmetro específico: um agente Java. O formato desse parâmetro é o seguinte:

-javaagent:C:\...\spring-agent.jar

[spring-agent.jar] é o agente Java necessário para que o JVM gerencie a configuração Spring / EclipseLink.

Ao executar um projeto, é possível passar argumentos para o JVM:

  • no [1], acessa-se as propriedades do projeto
  • em [2], as propriedades do Run
  • em [3], passa-se o parâmetro -javaagent para o JVM

5.13.3. InitDB

Agora, estamos prontos para testar novamente o [InitDB]. Desta vez, os resultados obtidos são os seguintes:

  • em [1], o teste foi bem-sucedido
  • em [2], na aba [Services], atualizamos a conexão do NetBeans com o banco de dados [dbpam_eclipselink]
  • em [3], foram criadas quatro tabelas
  • em [5], visualiza-se o conteúdo da tabela [employes]
  • em [6], o resultado.

5.13.4. JUnitDao

A execução da classe de testes [JUnitDao] pode falhar, mesmo que, com a implementação JPA / Hibernate, ela tenha sido bem-sucedida. Para entender o motivo, vamos analisar um exemplo.

O método testado é o seguinte método IndemniteDao.create:

package dao;

...
@Transactional(propagation=Propagation.REQUIRED)
public class IndemniteDao implements IIndemniteDao{

  @PersistenceContext
  private EntityManager em;

   // fabricante
  public IndemniteDao() {
  }

   // criar uma indenização
  public Indemnite create(Indemnite indemnite) {
    try{
      em.persist(indemnite);
    }catch(Throwable th){
      throw new PamException(th,31);
    }
    return indemnite;
  }

...
}
  • linhas 15-22: o método testado

O método de teste é o seguinte:


package dao;

...

public class JUnitDao {

// camadas DAO
  static private IEmployeDao employeDao;
  static private IIndemniteDao indemniteDao;
  static private ICotisationDao cotisationDao;

  @BeforeClass
  public static void init() {
    // log
    log("init");
    // configuração do aplicativo
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-DAO.xml");
    // camadas DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
  }

  @Before()
  public void clean() {
    // esvaziar o 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);
    }
  }

  // logs
  private static void log(String message) {
    System.out.println("----------- " + message);
  }

  // testes
….
  @Test
  public void test05() {
    log("test05");
    // criam-se duas indenizações com o mesmo índice
    // viola a restrição de exclusividade do índice
    boolean erreur = true;
    Indemnite indemnite1 = null;
    Indemnite indemnite2 = null;
    Throwable th = null;
    try {
      indemnite1 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
      indemnite2 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
      erreur = false;
    } catch (PamException ex) {
      th = ex;
      // verificações
      Assert.assertEquals(31, ex.getCode());
    } catch (Throwable th1) {
      th = th1;
    }
    // verificações
    Assert.assertTrue(erreur);
    // sequência de exceções
    System.out.println("Chaîne des exceptions --------------------------------------");
    System.out.println(th.getClass().getName());
    while (th.getCause() != null) {
      th = th.getCause();
      System.out.println(th.getClass().getName());
    }
    // a primeira indenização precisou ser mantida
    Indemnite indemnite = indemniteDao.find(indemnite1.getId());
    // verificação
    Assert.assertNotNull(indemnite);
    Assert.assertEquals(1, indemnite.getIndice());
    Assert.assertEquals(1.93, indemnite.getBaseHeure(), 1e-6);
    Assert.assertEquals(2, indemnite.getEntretienJour(), 1e-6);
    Assert.assertEquals(3, indemnite.getRepasJour(), 1e-6);
    Assert.assertEquals(12, indemnite.getIndemnitesCP(), 1e-6);
    // a segunda indenização não deveria ter sido mantida
    List<Indemnite> indemnites = indemniteDao.findAll();
    int nbIndemnites = indemnites.size();
    Assert.assertEquals(nbIndemnites, 1);
  }

...
}

Pergunta: explique o que o teste test05 faz e indique os resultados esperados.


Os resultados obtidos com uma camada JPA / Hibernate são os seguintes:

----------- test05
4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
ATTENTION: SQL Error: 1062, SQLState: 23000
4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
GRAVE: Duplicate entry '1' for key 2
Chaîne des exceptions --------------------------------------
exception.PamException
javax.persistence.EntityExistsException
org.hibernate.exception.ConstraintViolationException
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

O teste é aprovado, c.a.d. As asserções são verificadas e não há nenhuma exceção lançada pelo método de teste.


Pergunta: explique o que aconteceu.


Os resultados obtidos com a camada JPA / EclipseLink são os seguintes:

----------- teste05
[EL Warning]: 2010-06-04 16:48:26.421--UnitOfWork(749304)--Exceção [EclipseLink-4002] (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 2
Error Code: 1062
Call: INSERT INTO INDEMNITES (ID, ENTRETIEN_JOUR, REPAS_JOUR, INDICE, INDEMNITES_CP, BASE_HEURE, VERSION) VALUES (?, ?, ?, ?, ?, ?, ?)
        bind => [108, 2.0, 3.0, 1, 12.0, 1.93, 1]
Query: InsertObjectQuery(jpa.Indemnite[id=108,version=1,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0])
Chaîne des exceptions --------------------------------------
org.springframework.transaction.TransactionSystemException
javax.persistence.RollbackException
org.eclipse.persistence.exceptions.DatabaseException
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

Assim como anteriormente com o Hibernate, o teste é aprovado, c.a.d. As asserções foram verificadas e não houve nenhuma exceção lançada pelo método de teste.


Pergunta: explique o que aconteceu.



Pergunta: a partir desses dois exemplos, o que se pode concluir sobre a intercambiabilidade das implementações JPA? Ela é total neste caso?


5.13.5. Os outros testes

Uma vez que a camada [DAO] tenha sido testada e considerada correta, poderemos passar para os testes da camada [metier] e para os do próprio projeto em sua versão de console ou gráfica. A mudança na implementação JPA não afeta de forma alguma as camadas [metier] e [ui] e, portanto, se essas camadas funcionavam com o Hibernate, elas funcionarão com o EclipseLink, com algumas exceções: o exemplo anterior mostra, de fato, que as exceções lançadas pelas camadas [DAO] podem ser diferentes. Assim, no caso do teste, Spring / JPA / Hibernate lança uma exceção do tipo [PamException], uma exceção específica da aplicação [pam], enquanto Spring / JPA / EclipseLink, por sua vez, lança uma exceção do tipo [TransactionSystemException], uma exceção do framework Spring. Se, no caso de uso do teste, a camada [ui] espera uma exceção do tipo [PamException] porque foi construída com o Hibernate, ela deixará de funcionar quando passarmos para EclipseLink.

5.13.6. Tarefa a ser realizada


Trabalho prático: refazer os testes das aplicações console e swing com diferentes SGBD: MySQL5, Oracle XE, SQL Server.