5. Versão 1: Arquitetura Spring / JPA
Propomos escrever uma aplicação de consola, bem como uma aplicação gráfica, para gerar recibos de vencimento para prestadores de cuidados infantis empregados pela «Maison de la petite enfance» num município. Esta aplicação terá a seguinte arquitetura:
![]() |
5.1. O banco de dados
Os dados estáticos necessários para gerar o recibo de vencimento serão armazenados numa base de dados a que nos referiremos como dbpam. Esta base de dados poderá conter as seguintes tabelas:
Estrutura:
chave primária | |
número de versão – aumenta a cada modificação da linha | |
Número de Segurança Social do funcionário – único | |
Apelido do funcionário | |
nome | |
a morada deles | |
a cidade dele/dela | |
o seu código postal | |
Chave estrangeira no campo [ID] da tabela [INDEMNITES] |
O seu conteúdo pode ser o seguinte:

Estrutura:
chave primária | |
número de versão – aumenta a cada modificação da linha | |
Percentagem: Contribuição Social Geral + Contribuição para o Reembolso da Dívida Social | |
percentagem: contribuição social geral dedutível | |
percentagem: segurança social, viuvez, velhice | |
percentagem: pensão complementar + seguro de desemprego |
O seu conteúdo poderia ser o seguinte:
![]()
As taxas de contribuição para a segurança social são independentes do trabalhador. A tabela anterior tem apenas uma linha.
chave primária | |||
número de versão – aumenta a cada modificação da linha | |||
Índice de processamento – único | |||
Preço líquido em euros por uma hora de serviço de plantão | |||
Subsídio diário em euros por dia de cuidados | |||
Subsídio de refeição em euros por dia de assistência | |||
Subsídio de férias pagas. Trata-se de uma percentagem aplicada ao salário base. | |||
O seu conteúdo poderia ser o seguinte:

Note-se que os subsídios podem variar de um prestador de cuidados infantis para outro. Estes estão associados a um prestador de cuidados infantis específico através da sua categoria salarial. Assim, a Sra. Marie Jouveinal, que tem a categoria salarial 2 (tabela EMPLOYEES), tem um salário por hora de 2,1 euros (tabela INDEMNITES).
5.2. Método de cálculo do salário de uma ama
Apresentaremos agora o método de cálculo do salário mensal de uma ama. Este não pretende ser o método efetivamente utilizado na prática. A título de exemplo, utilizaremos o salário da Sra. Marie Jouveinal, que trabalhou 150 horas ao longo de 20 dias durante o período de pagamento.
São tidos em conta os seguintes fatores: | | |
O salário base do prestador de cuidados infantis é calculado pela seguinte fórmula: | ||
Um determinado montante de contribuições para a segurança social deve ser deduzida deste salário base : | | |
Total das contribuições para a segurança social: | ||
Além disso, a prestadora de cuidados infantis tem direito a um subsídio de subsistência e a um subsídio de refeição por cada dia trabalhado. Como tal, recebe os seguintes subsídios: | ||
No final, o salário líquido a pagar ao prestador de cuidados infantis é o seguinte: |
5.3. Como funciona a aplicação de consola
Aqui está um exemplo de execução da aplicação de consola numa janela do DOS:
Vamos escrever um programa que irá receber as seguintes informações:
- o número de segurança social da ama (254104940426058 no exemplo - linha 1)
- Número total de horas trabalhadas (150 no exemplo - linha 1)
- Número total de dias trabalhados (20 no exemplo - linha 1)
Vemos que:
- linhas 9–14: apresentam informações sobre o trabalhador cujo número de segurança social foi fornecido
- linhas 17–20: apresentam as taxas das várias contribuições
- linhas 23–26: apresentam os subsídios associados ao nível salarial do funcionário (aqui, nível 2)
- linhas 29–33: apresentam os componentes do salário a pagar
A aplicação assinala quaisquer 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 um número de segurança 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. Como funciona a aplicação gráfica
A aplicação gráfica calcula os salários dos prestadores de cuidados infantis utilizando um formulário Swing:
![]() |
- As informações passadas como parâmetros para o programa de consola são agora introduzidas através dos campos de entrada [1, 2, 3].
- O botão [4] inicia o cálculo do salário
- O formulário apresenta os vários componentes salariais até ao salário líquido a pagar [5]
A lista suspensa [1, 6] não exibe os números de segurança social dos funcionários, mas sim os seus nomes próprios e apelidos. Partimos do princípio de que não existem dois funcionários com os mesmos nomes próprios e apelidos.
5.5. Criação da base de dados
Iniciamos o WampServer e utilizamos a ferramenta PhpMyAdmin [1]:
![]() |
- em [2], selecione a opção [Bases de dados],
![]() |
- em [3], crie uma base de dados [dbpam_hibernate],
- em [4], a base de dados é criada. Selecione-a,
![]() |
- em [5], queremos importar um script SQL,
- em [6], utilize o botão [Procurar] para selecionar o ficheiro,
![]() |
- Em [7,8], selecione o script SQL,
- em [9], executamo-lo,
![]() |
- Em [10], as tabelas foram criadas. O seu conteúdo é o seguinte:
Tabela EMPLOYEES

tabela INDEMNIZAÇÕES

tabela CONTRIBUIÇÕES
![]()
5.6. Implementação JPA
5.6.1. Camada JPA / Hibernate
Iremos configurar a camada JPA no seguinte ambiente:
![]() |
Um programa de consola irá interagir com a base de dados. Para tal, é necessário:
- ter uma base de dados,
- ter o controlador JDBC para o SGBD, neste caso o MySQL,
- implementar a camada JPA utilizando o Hibernate,
- escrever o programa de consola.
Criamos o projeto Maven [mv-pam-jpa-hibernate] [1]:
![]() |
Na arquitetura da nossa aplicação, precisamos dos seguintes elementos:
- a base de dados,
- o controlador JDBC para o SGBD MySQL,
- a camada JPA/Hibernate (entidades e configuração),
- o programa da consola de testes.
5.6.1.1. A base de dados
Primeiro, vamos criar uma base de dados vazia. Iniciamos o WampServer e utilizamos a ferramenta PhpMyAdmin [1]:
![]() |
- em [2], selecione a opção [Bases de dados],
![]() |
- em [3], crie uma base de dados com o nome [dbpam_hibernate],
- em [4], a base de dados é criada.
5.6.1.2. Configurar a camada JPA
A ligação entre a camada JDBC e a base de dados é configurada no ficheiro [persistence.xml], que configura a camada JPA. Este ficheiro pode ser criado utilizando o NetBeans:
![]() |
- no separador [Serviços] [1], ligue-se à base de dados utilizando o controlador JDBC do MySQL [2],
- em [3], o nome da base de dados à qual pretende ligar-se.
- em [4], a URL JDBC da base de dados,
- em [5], inicie sessão como root sem palavra-passe,
- em [6], pode testar a ligação,
- em [7], a ligação foi bem-sucedida.
![]() |
- A ligação aparece em [8] e [9],
- em [10], adicione um novo elemento ao projeto,
![]() |
- em [11] selecione a categoria [Persistência] e em [12] o elemento [Unidade de Persistência],
- em [13], nomeie esta unidade de persistência,
- em [14], selecionamos uma implementação do Hibernate,
- em [15], especifique a ligação que acabámos de criar à base de dados MySQL,
- Em [16], especifique que, quando a camada JPA for instanciada, ela deve criar as tabelas correspondentes às entidades JPA do projeto.
No final do assistente, é gerado o ficheiro [persistence.xml]:
![]() |
- o ficheiro aparece num novo ramo do projeto, na pasta [META-INF] [1],
- que corresponde à pasta [src/main/resources] do projeto [2,3].
O 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ção. RESOURCE_LOCAL indica que o projeto gere as transações por conta própria. Neste caso, o programa de consola será responsável por fazê-lo,
- linha 4: a implementação JPA utilizada é o Hibernate,
- linhas 6–9: as propriedades JDBC para a ligação à base de dados,
- linha 11: solicita a criação de tabelas correspondentes às entidades JPA. Na verdade, o NetBeans gera uma configuração incorreta aqui. A configuração deve ser a seguinte:
<property name="hibernate.hbm2ddl.auto" value="create"/>
Com a opção create, o Hibernate, aquando da instanciação da camada JPA, elimina e, em seguida, cria as tabelas correspondentes às entidades JPA. A opção create-drop faz o mesmo, mas, no final do ciclo de vida da camada JPA, elimina todas as tabelas. Existe outra opção:
<property name="hibernate.hbm2ddl.auto" value="update"/>
Esta opção cria as tabelas se estas não existirem, mas não as elimina se já existirem.
Iremos adicionar 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"/>
Estas configurações instruem o Hibernate a apresentar as instruções SQL que envia para a base de dados. O ficheiro completo é, portanto, 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>
<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. Dependências
Voltemos à arquitetura do projeto:
![]() |
Configurámos a camada JPA através do ficheiro [persistence.xml]. A implementação escolhida foi o Hibernate. Isto introduziu dependências no projeto:
![]() |
Estas dependências devem-se à inclusão do Hibernate no projeto. Precisamos de adicionar outra dependência: o controlador JDBC do MySQL, que implementa a camada JDBC da arquitetura. Atualizamos o ficheiro [pom.xml] da seguinte forma:
<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–12 adicionam a dependência do controlador JDBC do MySQL.
5.6.1.4. Entidades JPA
![]() |
Pergunta: Seguindo a abordagem do exemplo da Secção 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 primária @ManyToOne será criada. A relação inversa @OneToMany não será criada.
5.6.1.5. O código para a classe principal
Incluímos as entidades JPA desenvolvidas anteriormente [1] no projeto:
![]() |
depois 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) {
// creating the Entity Manager is enough to build the JPA layer
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpa-hibernatePU");
EntityManager em=emf.createEntityManager();
// resource release
em.close();
emf.close();
}
}
- Linha 10: Criamos o EntityManagerFactory para a unidade de persistência denominada [mv-pam-jpa-hibernatePU]. Este nome provém do ficheiro [persistence.xml]:
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
...
</persistence-unit>
- Linha 12: O EntityManager é criado. Isto cria a camada JPA. O ficheiro [persistence.xml] será utilizado e, assim, as tabelas da base de dados serão criadas.
- linhas 14–15: os recursos são libertados.
5.6.1.6. Testes
Voltemos à arquitetura do nosso projeto:
![]() |
Todas as camadas foram implementadas. Executamos o projeto [2].
![]() |
A saída da consola é a seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
A consola contém apenas registos do Hibernate, uma vez que o programa executado não faz nada além de instanciar a camada JPA. Tenha em atenção os seguintes pontos:
- linha 43: o Hibernate tenta eliminar a chave estrangeira da tabela [EMPLOYEES],
- linhas 51–55: eliminação das três tabelas,
- linha 57: criação da tabela [COTISATIONS],
- linha 67: criação da tabela [EMPLOYEES],
- linha 80: criação da tabela [INDEMNITIES],
- linha 91: criação da chave estrangeira para a tabela [EMPLOYEES].
No NetBeans, pode visualizar as tabelas na ligação criada anteriormente:
![]() |
As tabelas criadas dependem tanto da implementação da camada JPA utilizada como do SGBD utilizado. Assim, uma implementação JPA/EclipseLink com a mesma base de dados pode gerar tabelas diferentes. É isso que vamos ver agora.
5.6.2. Camada JPA / EclipseLink
Vamos criar um novo projeto Maven no seguinte ambiente:
![]() |
Seguiremos os passos da secção anterior:
- criar uma base de dados MySQL [dbpam_eclipselink]. Iremos utilizar o script [dbpam_eclipselink.sql] para a gerar,
- criar o ficheiro [persistence.xml] do projeto. Utilize a implementação EclipseLink JPA 2.0,
- adicionar a dependência do controlador JDBC do MySQL às dependências geradas,
- adicionar as entidades JPA e o programa de consola,
- execute os testes.
O ficheiro [persistence.xml] terá o seguinte conteúdo:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="pam-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–13 foram geradas pelo assistente do NetBeans,
- Linha 14: Esta propriedade permite-nos definir o nível de registo do EclipseLink. O nível FINE permite-nos ver as instruções SQL que o EclipseLink irá executar na base de dados,
- Linha 15: Quando a camada JPA/EclipseLink é instanciada, as tabelas de entidades JPA serão eliminadas e, em seguida, criadas.
A saída da consola é a seguinte:
- linhas 26-30: ligação à base de dados MySQL,
- linhas 31-34: confirmação de que a ligação foi bem-sucedida,
- linha 36: eliminar a chave estrangeira da tabela [EMPLOYEES],
- linha 37: eliminação da tabela [COTIZAÇÕES],
- linha 38: criação da tabela [CONTRIBUTIONS]. Vale a pena notar que a chave primária ID não possui o atributo auto_increment do MySQL. Isto significa que o MySQL não gera os valores da chave primária,
- linha 39: eliminação da tabela [EMPLOYEES],
- linha 40: criar a tabela [EMPLOYEES]. A sua chave primária ID não possui o atributo auto_increment do MySQL,
- linha 41: eliminação da tabela [INDEMNITIES],
- linha 42: criação da tabela [INDEMNITIES]. A sua chave primária ID não possui o atributo auto_increment do MySQL,
- linha 43: criar uma chave estrangeira da tabela [EMPLOYEES] para a tabela [BENEFITS],
- linha 44: criação da tabela [SEQUENCE]. Será utilizada para gerar as chaves primárias das três tabelas anteriores,
- linha 47: ocorre uma exceção porque esta tabela já existia,
- linhas 51–53: inicialização da tabela [SEQUENCE].
A existência das tabelas geradas pode ser verificada no NetBeans [1]:
![]() |
Portanto, com base nas mesmas entidades JPA, as implementações JPA do Hibernate e do EclipseLink não geram as mesmas tabelas. No restante deste documento, quando a implementação JPA utilizada for:
- Hibernate, utilizaremos a base de dados [dbpam_hibernate],
- EclipseLink, utilizaremos a base de dados [dbpam_eclipselink].
5.6.3. Trabalho a realizar
Seguindo o mesmo procedimento de antes,
- crie e teste um projeto [mv-pam-jpa-hibernate-oracle] utilizando uma implementação do Hibernate JPA e um SGBD Oracle,
- crie e teste um projeto [mv-pam-jpa-hibernate-mssql] utilizando uma implementação Hibernate JPA e um SGBD SQL Server,
- crie e teste um projeto [mv-pam-jpa-eclipselink-oracle] utilizando uma implementação JPA do EclipseLink e um SGBD Oracle,
- criar e testar um projeto [mv-pam-jpa-eclipselink-mssql] utilizando uma implementação EclipseLink JPA e um SGBD SQL Server,
5.6.4. Lazy ou Eager?
Voltemos a uma possível definição da entidade [Employee]:
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–29 definem a chave estrangeira da tabela [EMPLOYEES] para a tabela [INDEMNITIES]. O atributo fetch na linha 27 define a estratégia de recuperação para o campo indemnity na linha 29. Existem dois modos:
- FetchType.LAZY: Quando um funcionário é consultado, a indemnização correspondente não é recuperada. Será recuperada quando o campo [Employee].indemnity for referenciado pela primeira vez.
- FetchType.EAGER: Quando um funcionário é pesquisado, o subsídio correspondente é recuperado. Este é o modo predefinido quando nenhum modo é especificado.
Para compreender a vantagem da opção FetchType.LAZY, considere o seguinte exemplo. Uma lista de funcionários sem a respetiva remuneração é apresentada numa página web com um link [Detalhes]. Ao clicar neste link, é apresentada a remuneração do funcionário selecionado. Observamos que:
- Para apresentar a primeira página, não precisamos dos funcionários juntamente com os seus benefícios. O modo FetchType.LAZY é, portanto, adequado;
- para exibir a segunda página com os detalhes, é necessário efetuar uma consulta adicional à base de dados para recuperar os benefícios do funcionário selecionado.
O modo FetchType.LAZY evita a recuperação de dados em excesso de que a aplicação não necessita imediatamente. Vejamos um exemplo.
O projeto [mv-pam-jpa-hibernate] é duplicado:
![]() |
- em [1], o projeto é copiado,
- em [2], especificamos a pasta para a cópia e, em [3], o seu nome,
- em [4], o novo projeto tem o mesmo nome que o antigo. Alteramos isto:
![]() |
- em [1], renomeamos o projeto,
- em [2], renomeamos o projeto e o seu artifactId,
- em [3], o novo projeto.
Modificamos o programa [Main.java] da seguinte forma:
package main;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import jpa.Employe;
public class Main {
// the JPQL query below brings back an employee
// the foreign key [Employe].indemnite is in FetchType.LAZY
public static void main(String[] args) {
// creating the Entity Manager is enough to build the JPA layer
EntityManagerFactory emf = Persistence.createEntityManagerFactory("pam-jpa-hibernatePU");
// first attempt
EntityManager em = emf.createEntityManager();
Employe employe = (Employe) em.createQuery("select e from Employe e where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
em.close();
// we display the employee
try {
System.out.println(employe);
} catch (Exception ex) {
System.out.println(ex);
}
// second test
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();
// free up resources
em.close();
// we display the employee
try {
System.out.println(employe);
} catch (Exception ex) {
System.out.println(ex);
}
// resource release
emf.close();
}
}
- linha 15: criamos o EntityManagerFactory para a camada JPA,
- linha 17: obtemos o EntityManager, que nos permite interagir com a camada JPA,
- linha 18: recuperamos o funcionário chamado Jouveinal,
- linha 19: fechamos o EntityManager. Isto fecha o contexto de persistência.
- linha 22: exibimos o funcionário recuperado.
A classe [Employee] é 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 é recuperado no modo LAZY,
- linha 47: utiliza o campo indemnite. Se o método toString for chamado enquanto o campo indemnite ainda não tiver sido recuperado, este será recuperado nesse momento. A menos que o contexto de persistência tenha sido fechado, como no exemplo.
Voltemos ao código [Main]:
- linhas 21–25: devemos obter uma exceção. Isto porque o método toString será chamado. Ele utilizará o campo indemnite. Este campo será procurado. Uma vez que o contexto de persistência foi fechado, a entidade [Employee] recuperada já não existe, daí a exceção.
- linha 27: criamos um novo EntityManager,
- linha 28: recuperamos o funcionário Jouveinal solicitando explicitamente o subsídio associado na consulta JPQL. Esta solicitação explícita é necessária porque o modo de recuperação para este subsídio é LAZY,
- Linha 30: Fechamos o EntityManager,
- Linhas 32–36: exibimos o funcionário novamente. Não deve ocorrer nenhuma exceção.
Para executar o projeto, é necessário dispor de uma base de dados com dados. Deverá criá-la seguindo os passos descritos na secção 5.5. Além disso, o ficheiro [persistence.xml] deve ser alterado:
<?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>
- Removemos a opção que criava as tabelas. A base de dados aqui já existe e está preenchida,
- e removemos as opções que faziam com que o Hibernate registasse as instruções SQL enviadas para a base de dados.
A execução do projeto gera as duas mensagens seguintes na consola:
- Linha 1: A exceção que ocorreu ao tentar recuperar a remuneração em falta enquanto a sessão estava encerrada. Podemos ver que a remuneração não foi recuperada devido ao modo LAZY,
- linha 2: o funcionário com o seu subsídio recuperado através de uma consulta que contornou o modo LAZY.
5.6.5. Trabalho a realizar
Seguindo um procedimento semelhante ao que acabámos de descrever, crie um projeto [mv-pam-pa-eclipselink-lazy] que demonstre o comportamento do EclipseLink com o modo LAZY.
Os seguintes resultados são obtidos:
No modo LAZY, ambas as consultas devolveram a remuneração juntamente com o funcionário. Ao pesquisar esta anomalia online, descobrimos que a anotação [FetchType.LAZY] (linha 1):
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
não é um requisito, mas sim uma sugestão. O implementador do JPA não é obrigado a segui-la. Podemos ver, portanto, que o código por vezes torna-se dependente da implementação do JPA utilizada. É possível configurar o EclipseLink para se comportar como esperado no modo LAZY.
5.6.6. Seguindo em frente
A arquitetura da aplicação a ser construída é a seguinte:
![]() |
No restante deste documento, iremos duplicar o projeto Maven [mv-pam-jpa-hibernate] para o projeto [mv-pam-spring-hibernate] [1, 2, 3]:
![]() |
- depois, iremos renomear o novo projeto [4, 5, 6].
Vamos alterar as dependências do novo projeto. O ficheiro [pom.xml] fica com 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: dependências para o pool de conexões Apache DBCP,
- linhas 42–65: dependências para o framework Spring,
- linhas 67–71: dependências para a implementação JPA/Hibernate,
- linhas 72–76: a dependência do controlador JDBC do MySQL,
- linhas 77–81: a dependência para a interface Swing. Esta é adicionada automaticamente pelo NetBeans quando uma interface Swing é adicionada ao projeto.
Além disso, iremos gerar as duas bases de dados MySQL:
- [dbpam_hibernate] a partir do script [dbpam_hibernate.sql],
- [dbpam_eclipselink] a partir do script [dbpam_eclipselink.sql],
5.7. As interfaces im s para as camadas [business] e [DAO]
Voltemos à arquitetura da aplicação:
![]() |
Na arquitetura acima, que interface deve a camada [DAO] fornecer à camada [business] e que interface deve a camada [business] fornecer à camada [UI]? Uma primeira abordagem para definir as interfaces das diferentes camadas consiste em examinar os vários casos de utilização da aplicação. Aqui temos dois, dependendo da interface de utilizador escolhida: consola ou formulário gráfico.
Vamos examinar como a aplicação de consola é utilizada:
A aplicação recebe três informações do utilizador (ver linha 1 acima)
- o número de segurança social do prestador de cuidados infantis
- o número de horas trabalhadas no mês
- o número de dias trabalhados no mês
Com base nestas informações e noutros dados armazenados em ficheiros de configuração, a aplicação apresenta as seguintes informações:
- linhas 4–6: os valores introduzidos
- linhas 8–10: informações relacionadas com o funcionário cujo número de segurança social foi fornecido
- linhas 12–14: as taxas das várias contribuições para a segurança social
- linhas 16–17: os vários subsídios pagos ao prestador de cuidados infantis
- linhas 19–24: itens do recibo de vencimento do prestador de cuidados infantis
A camada [empresarial] deve fornecer uma determinada quantidade de informações à camada [UI]:
- informações relacionadas com um prestador de cuidados infantis identificado pelo seu número de segurança social. Estas informações encontram-se na tabela [EMPLOYEES]. Isto permite que as linhas 6–8 sejam apresentadas.
- os montantes das várias taxas de contribuição para a segurança social a deduzir do salário bruto. Estas informações encontram-se na tabela [COTISATIONS]. Isto permite que as linhas 10–12 sejam apresentadas.
- os montantes dos vários subsídios relacionados com a função de prestador de cuidados infantis. Esta informação encontra-se na tabela [INDEMNITES]. Isto permite que as linhas 14–15 sejam apresentadas.
- os componentes do salário apresentados nas linhas 18 a 22.
A partir daí, poderíamos decidir sobre uma implementação inicial da interface [IMetier] apresentada pela camada [metier] à camada [ui]:
- linha 1: os elementos da camada [business] são colocados no pacote [business]
- linha 5: o método [calculatePaystub] recebe como parâmetros as três informações obtidas pela camada [ui] e devolve um objeto do tipo [Paystub] contendo as informações que a camada [ui] irá apresentar na consola. A classe [ Paystub] poderia ser a seguinte:
- linha 9: o funcionário abrangido pelo recibo de vencimento - informação n.º 1 apresentada pela camada [ui]
- linha 10: as várias taxas de contribuição - informação n.º 2 apresentada pela camada [ui]
- linha 11: os vários subsídios associados ao índice do funcionário - informação n.º 3 apresentada pela camada [ui]
- linha 12: os componentes do seu salário - informação n.º 4 apresentada pela camada [ui]
Um segundo caso de utilização da camada [business] surge com a interface gráfica:
![]() |
Como mostrado acima, a lista suspensa [1, 2] exibe todos os funcionários. Esta lista deve ser solicitada à camada [business]. A interface ace para esta camada evolui então da seguinte forma:
- linha [10]: o método que permitirá à camada [UI] solicitar a lista de todos os funcionários à camada [Business].
A camada [business] só pode inicializar os campos [Employee, Contribution, Allowance] do objeto [Payroll] acima através de uma consulta à camada [DAO], uma vez que esta informação está armazenada nas tabelas da base de dados. O mesmo se aplica à recuperação da lista de todos os funcionários. Poderíamos criar uma única interface [DAO] para gerir o acesso às três entidades [Empregado, Contribuição, Subsídio]. No entanto, decidimos aqui criar uma interface [DAO] por entidade.
A interface [DAO] para aceder às entidades [Contribution] na tabela [CONTRIBUTIONS] será a seguinte:
- Linha 6: A interface [ICotisationDao] gere o acesso à entidade [Cotisation] e, consequentemente, à tabela [COTISATIONS] na base de dados. A nossa aplicação necessita apenas do método [findAll] na linha 16, que recupera todo o conteúdo da tabela [COTISATIONS]. Aqui, pretendíamos 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] elimina uma entidade [Cotisation] existente
- Linha 14: O método [find] recupera uma entidade [Cotisation] existente utilizando o seu id
- Linha 16: O método [findAll] devolve uma lista de todas as entidades [Membership] existentes
Vamos analisar mais detalhadamente a assinatura do método [create]:
O método create tem um parâmetro cotisation do tipo Cotisation. O parâmetro cotisation deve ser persistido, ou seja, armazenado na tabela [COTISATIONS]. Antes desta persistência, o parâmetro cotisation tem um identificador id sem valor. Após a persistência, o campo id tem um valor que é a chave primária do registo 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 devolva adicionalmente o parâmetro cotisation como resultado. Uma vez que o método de chamada mantém uma referência ao objeto [Cotisation cotisation], se este for modificado, o método de chamada tem acesso ao objeto modificado porque mantém uma referência ao mesmo. Pode, portanto, conhecer o valor que o método create atribuiu ao campo id do objeto [Cotisation cotisation]. A assinatura do método poderia, portanto, ser simplificada para:
Ao escrever uma interface, é importante lembrar que esta pode ser utilizada em dois contextos diferentes: local e remoto . No contexto local, o método chamador e o método chamado são executados na mesma JVM:
![]() |
Se a camada [business] chamar o método create da camada [DAO], ela tem, de facto, uma referência ao parâmetro [Membership membership] que passa para o método.
No contexto remoto, o método chamador e o método chamado são executados em JVMs diferentes:
![]() |
No exemplo acima, a camada [business] é executada na JVM 1 e a camada [DAO] na JVM 2, em duas máquinas diferentes. As duas camadas não comunicam diretamente entre si. Entre elas existe uma camada a que chamaremos de camada de comunicação [1]. Esta consiste numa camada de transmissão [2] e numa camada de receção [3]. O programador geralmente não precisa de escrever estas camadas de comunicação. Elas são geradas automaticamente por ferramentas de software. A camada [business] é escrita como se estivesse a ser executada na mesma JVM que a camada [DAO]. Por conseguinte, não são necessárias alterações no código.
O mecanismo de comunicação entre a camada [de negócios] e a camada [DAO] é o seguinte:
- a camada [negócio] chama o método create da camada [DAO], passando-lhe o parâmetro [Contribution contribution1]
- este parâmetro é, na verdade, passado para a camada de transmissão [2]. Esta camada transmitirá o valor do parâmetro cotisation1 pela rede, e não a sua referência. A forma exata deste valor depende do protocolo de comunicação utilizado.
- A camada de receção [3] recupera este valor e utiliza-o para reconstruir um objeto [Cotisation cotisation2] que espelha o parâmetro inicial enviado pela camada [business]. Temos agora dois objetos idênticos (em termos de conteúdo) em duas JVMs diferentes: cotisation1 e cotisation2.
- A camada de apresentação passará o objeto `contribution2` para o método `create` da camada [DAO], que o persistirá na base de dados. Após esta operação, o campo `id` do objeto `contribution2` foi inicializado com a chave primária do registo adicionado à tabela [COTISATIONS]. Este não é o caso do objeto `contribution1`, ao qual a camada [business] tem uma referência. Se quisermos que a camada [business] tenha uma referência ao objeto cotisation2, temos de o passar para a camada. Por conseguinte, precisamos de alterar a assinatura do método create na camada [DAO]:
- Com esta nova assinatura, o método create irá devolver o objeto persistido contribution2. Este resultado é devolvido à camada recetora [3] que chamou a camada [DAO]. A camada [DAO] irá devolver o valor (não a referência) de contribution2 à camada emissora [2].
- A camada emissora [2] irá recuperar este valor e utilizá-lo para reconstruir um objeto [Membership membership3] que reflete o resultado devolvido pelo método create da camada [DAO].
- O objeto [Contribution contribution3] é devolvido ao método na camada [business] cuja chamada ao método create da camada [DAO] tinha iniciado todo este mecanismo. A camada [business] pode, portanto, determinar o valor da chave primária atribuído ao objeto [Contribution contribution1] para o qual tinha solicitado persistência: este é o valor do campo id em contribution3.
A arquitetura anterior não é a mais comum. Mais frequentemente, as camadas [business] e [DAO] encontram-se na mesma JVM:
![]() |
Nesta arquitetura, são os métodos da camada [business] que devem devolver resultados, e não os da camada [DAO]. No entanto, a seguinte assinatura do método create da camada [DAO]:
permite-nos evitar fazer suposições sobre a arquitetura efetivamente implementada. Utilizar assinaturas que funcionem independentemente da arquitetura escolhida — seja local ou remota — significa que, se um método chamado modificar alguns dos seus parâmetros:
- estes 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 aos parâmetros modificados que passou para o método chamado.
Isto permite-nos fazer a transição de uma arquitetura local para uma arquitetura remota sem modificar o código. Vamos rever a interface [ICotisationDao] à luz disto:
- linha 8: o caso do método create foi tratado
- Linha 10: O método edit utiliza o seu parâmetro [Cotisation cotisation1] para atualizar o registo na tabela [COTISATIONS] que tem a mesma chave primária que o objeto cotisation. Ele devolve o objeto cotisation2, que é uma representação do registo modificado. O próprio parâmetro contribution1 não é modificado. O método deve devolver contribution2 como resultado, quer numa arquitetura remota, quer numa local.
- Linha 12: O método destroy elimina o registo da tabela [COTISATIONS] que tem a mesma chave primária que o objeto contribution passado como parâmetro. O objeto contribution não é modificado. Por conseguinte, não precisa de ser devolvido.
- Linha 14: O parâmetro id do método find não é modificado pelo método. Não precisa de ser incluído no resultado.
- Linha 16: O método findAll não tem parâmetros. Por isso, não precisamos de o examinar.
Em última análise, apenas a assinatura do método create precisa de ser adaptada para ser utilizável numa arquitetura remota. O raciocínio acima aplica-se às outras interfaces [DAO]. Não o repetiremos aqui e, em vez disso, utilizaremos assinaturas que sejam utilizáveis tanto em arquiteturas remotas como locais.
A interface [DAO] para aceder a entidades [Indemnite] na tabela [INDEMNITES] será a seguinte:
- Linha 6: A interface [IIndemniteDao] gere o acesso à entidade [Indemnite] e, consequentemente, à tabela [INDEMNITES] na base de dados. A nossa aplicação necessita apenas do método [findAll] na linha 16, que recupera todo o conteúdo da tabela [INDEMNITES]. Aqui, pretendíamos 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] elimina uma entidade [Indemnite] existente
- Linha 14: O método [find] recupera uma entidade [Indemnite] existente utilizando o seu id
- Linha 16: O método [findAll] devolve uma lista de todas as entidades [Indemnite] existentes
A interface [DAO] para aceder a entidades [Employe] na tabela [EMPLOYES] será a seguinte:
- Linha 6: A interface [IEmployeDao] gere o acesso à entidade [Employee] e, consequentemente, à tabela [EMPLOYEES] na base de dados. A nossa aplicação necessita apenas do método [findAll] na linha 16, que recupera todo o conteúdo da tabela [EMPLOYEES]. Aqui, pretendíamos 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 [Employee]
- Linha 10: O método [edit] modifica uma entidade [Employee] existente
- Linha 12: O método [destroy] elimina uma entidade [Employee] existente
- Linha 14: O método [find] recupera uma entidade [Employee] existente através do seu ID
- Linha 16: O método [find(String SS)] recupera uma entidade [Employee] existente utilizando o seu número SS. Vimos que este método era necessário para a aplicação de consola.
- Linha 18: O método [findAll] devolve uma lista de todas as entidades [Employee] existentes. Vimos que este método era necessário para a aplicação gráfica.
5.8. A classe [PamException]
A camada [DAO] irá funcionar com a API JDBC do Java. Esta API lança exceções [SQLException] controladas, que têm duas desvantagens:
- elas sobrecarregam o código, que deve lidar com essas exceções usando blocos try/catch.
- devem ser declaradas nas assinaturas dos métodos da interface [IDao] utilizando "throws SQLException". Isto impede a implementação desta interface por classes que lançariam uma exceção controlada de um tipo diferente de [SQLException].
Para resolver este problema, a camada [DAO] irá apenas «propagar» exceções não verificadas 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 que está a ser utilizada
- A camada [DAO] lança exceções não capturadas do tipo [PamException]
Isto tem duas consequências:
- A camada [business] não será obrigada a tratar exceções da camada [DAO] utilizando blocos try/catch. Pode simplesmente deixá-las propagar-se até à camada [UI].
- Os métodos da interface [IDao] não precisam de especificar a natureza da [PamException] nas suas assinaturas, o que deixa em aberto a possibilidade de implementar esta interface com classes que lançariam outro tipo de exceção não capturada.
A classe [PamException] será colocada no pacote [exception] do projeto NetBeans:
![]() |
O seu código é o seguinte:
- Linha 4: [PamException] deriva de [RuntimeException]. É, portanto, um tipo de exceção que o compilador não exige que seja tratada com um bloco try/catch nem incluída nas assinaturas de métodos. Por este motivo, [PamException] não está incluída nas assinaturas de método da interface [IDao]. Isto permite que a interface seja implementada por uma classe que lance um tipo diferente de exceção, desde que também derive de [RuntimeException].
- Para distinguir entre os erros que podem ocorrer, utilizamos o código de erro na linha 7. Os três construtores nas linhas 14, 19 e 24 são os da classe pai [RuntimeException], aos quais adicionámos um parâmetro: o código de erro que queremos atribuir à exceção.
O comportamento da aplicação, do ponto de vista das exceções, será o seguinte:
- A camada [DAO] encapsulará qualquer exceção encontrada numa [PamException] e a re-lançará para a camada [business].
- A camada [business] permitirá que as exceções lançadas pela camada [DAO] se propaguem para cima. Ela encapsulará qualquer exceção que ocorra na camada [business] numa [PamException] e a relançará para a camada [UI].
- A camada [UI] intercepta todas as exceções propagadas pelas camadas [business] e [DAO]. Ela simplesmente exibirá a exceção na consola ou na interface gráfica do utilizador.
Vamos agora examinar a implementação das camadas [DAO] e [business] sucessivamente.
5.9. A camada [DAO] da aplicação [PAM]
Estamos a trabalhar com a seguinte arquitetura:
![]() |
5.9.1. Implementação
Leitura recomendada: Secção 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 irá capturar qualquer exceção e envolvê-la numa [PamException] com um código de erro específico para a exceção capturada.
As classes de implementação farão parte do pacote [dao]:
![]() |
5.9.2. Configuração
Leitura recomendada: Secção 3.1.5 de [ref1]
A integração DAO/JPA é configurada pelo ficheiro Spring [spring-config-dao.xml] e pelo ficheiro JPA [persistence.xml]:
![]() |
Pergunta: Escreva o conteúdo destes dois ficheiros. Partiremos do princípio de que a base de dados utilizada é a base de dados MySQL5 [dbpam_hibernate] gerada pelo script SQL [dbpam_hibernate.sql]. O ficheiro Spring definirá os seguintes três beans: employeDao do tipo EmployeDao, indemniteDao do tipo IndemniteDao e cotisationDao do tipo CotisationDao. Além disso, a implementação JPA utilizada será o Hibernate.
5.9.3. Testes
Leitura recomendada: secções 3.1.6 e 3.1.7 de [ref1]
Agora que a camada [DAO] está escrita e configurada, podemos testá-la. A arquitetura de teste será a seguinte:
![]() |
5.9.4. InitDB
Iremos criar dois programas de teste para a camada [DAO]. Estes serão colocados no pacote [dao] [2] do ramo [Test Packages] [1] do projeto NetBeans. Este ramo não está incluído no projeto gerado pela opção [Build project], o que garante que os programas de teste que colocarmos aí não serão incluídos no ficheiro .jar final do projeto.
![]() |
As classes colocadas no ramo [Test Packages] têm acesso às classes do ramo [Source Packages], bem como às bibliotecas de classes do projeto. Se os testes exigirem bibliotecas diferentes das que estão no 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 executa quaisquer testes. Preenche a base de dados com alguns registos e, em seguida, apresenta-os na consola.
- [JUnitDao] realiza uma série de testes e verifica os seus resultados.
A estrutura da classe [JUnitInitDB] é a seguinte:
- O método [init] é executado antes do início do conjunto de testes (anotação @BeforeClass). Ele instancia a camada [DAO].
- O método [clean] é executado antes de cada teste (anotação @Before). Limpa a base de dados.
- O método [initDB] é um teste (anotação @Test). É o único. Um teste deve conter instruções de asserção Assert.assertCondition. Aqui, não haverá nenhuma. O método é, portanto, um teste fictício. O seu objetivo é preencher a base de dados com algumas linhas e, em seguida, apresentar o conteúdo da base de dados na consola. Os métodos create e findAll das camadas [DAO] são utilizados aqui.
Pergunta: Complete o código para a classe [JUnitInitDB]. Use o exemplo da Secção 3.1.6 de [ref1] como guia. O código irá gerar a saída apresentada na Secção 5.1.
5.9.5. Implementação do teste
Estamos agora prontos para executar [InitDB]. Descrevemos o procedimento utilizando o SGBD MySQL5:
![]() |
- as classes [1], os ficheiros de configuração [2] e as classes de teste da camada [DAO] [3] estão configurados,
![]() |
- o projeto é compilado [4]
- a classe [JUnitInitDB] é executada [5]. O SGBD MySQL5 é iniciado com uma base de dados [dbpam_hibernate] existente,
- a janela [Resultados do Teste] [6] indica que os testes foram bem-sucedidos. Esta mensagem não é significativa neste contexto, uma vez que o programa [JUnitInitDB] não contém instruções de asserção Assert.assertCondition que possam causar a falha do teste. No entanto, mostra que não ocorreram exceções durante a execução do teste.
A janela [Output] contém os registos de execução, incluindo os do Spring e do próprio teste. A saída gerada pela classe [JUnitInitDB] é a seguinte:
As tabelas [EMPLOYEES, ALLOWANCES, CONTRIBUTIONS] foram preenchidas. Isto pode ser verificado ligando o NetBeans à base de dados [dbpam_hibernate].
![]() |
- Em [1], no separador [serviços], pode visualizar os dados da tabela [employees] da ligação [dbpam_hibernate] [2],
- em [3] o resultado.
5.9.6. JUnitD ao
Vamos agora analisar uma segunda classe de teste [JUnitDao]:
![]() |
A estrutura da classe será a seguinte:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | |
Na classe de teste anterior, a base de dados é limpa antes de cada teste.
Pergunta: Escreva os seguintes métodos:
1 - test02: com base em test01
2 - test03: Um funcionário tem um campo do tipo Indemnização. Por isso, crie uma entidade Indemnização e uma entidade Funcionário
3 - test04.
Procedendo da mesma forma que para a classe de teste [JUnitInitDB], obtemos os seguintes resultados:
![]() |
- Em [1], executamos a classe de teste
- em [2], os resultados do teste na janela [Resultados do Teste]
Vamos provocar um erro para ver como é relatado na página de resultados:
Linha 13: A verificação irá causar um erro, uma vez que o valor de Csgrds é 3,49 (linha 8). A execução da classe de teste produz os seguintes resultados:
![]() |
- A página de resultados [1] mostra agora que alguns testes falharam.
- Em [2], um resumo da exceção que causou a falha do teste. Inclui o número da linha no código Java onde a exceção ocorreu.
5.10. A camada [de negócios] da aplicação [PAM]
Agora que a camada [DAO] foi escrita, passamos a estudar a camada de negócios [2]:
![]() |
5.10.1. A interface Java [IMetier]
Isto foi descrito na secção 5.7. Recordamo-lo a seguir:
A implementação da camada [business] será feita num pacote [business]:
![]() |
O pacote [Negócio] incluirá, além da interface [IMetier] e da sua implementação [Metier], duas outras classes: [Payroll] e [PayrollItems]. A classe [Payroll] foi brevemente apresentada na Secção 5.7. Vamos agora revisá-la.
5.10.2. A classe [Payroll]
O método [calculatePayStub] da interface [IMetier] devolve um objeto do tipo [PayStub] que representa os vários elementos de um recibo de vencimento. A sua definição é a seguinte:
- linha 7: a classe implementa a interface Serializable porque as suas instâncias podem ser trocadas através da rede.
- linha 9: o funcionário abrangido pelo recibo de vencimento
- linha 10: as várias taxas de contribuição
- linha 11: os diversos subsídios associados ao índice do trabalhador
- linha 12: os componentes do seu salário
- linhas 14–22: os dois construtores da classe
- linhas 25–27: método [toString] que identifica um objeto [PayStub] específico
- Linhas 29 e seguintes: acessores públicos aos campos privados da classe
A classe [ElementsSalaire] referenciada na linha 11 da classe [FeuilleSalaire] acima contém os elementos que compõem um recibo de vencimento. A sua definição é a seguinte:
- linha 3: a classe implementa a interface Serializable porque é um componente da PayrollClass, que deve ser serializável.
- linha 6: o salário base
- linha 7: contribuições para a segurança social pagas sobre este salário base
- linha 8: pagamentos diários de pensão de alimentos
- linha 9: os subsídios diários para refeições das crianças
- linha 10: o salário líquido a pagar ao prestador de cuidados infantis
- linhas 12–24: construtores de classe
- linhas 27–31: método [toString] que identifica um objeto [ElementsSalaire] específico
- Linhas 34 e seguintes: acessores públicos aos campos privados da classe
5.10.3. A classe de implementação [Metier] da camada [business]
A classe de implementação [Metier] da camada [business] poderia ser a seguinte:
- linha 5: A anotação @Transactional do Spring garante que todos os métodos da classe sejam executados dentro de uma transação.
- linhas 9-10: referências às camadas [DAO] das entidades [Cotisation, Employe, Indemnite]
- linhas 14–17: o método [calculatePayroll]
- linhas 20–22: o método [findAllEmployees]
- Linha 24 e seguintes: os acessores públicos para os campos privados da classe
Pergunta: Escreva o código para o método [findAllEmployees].
Pergunta: Escreva o código para o método [calculatePayroll].
Tenha em atenção os seguintes pontos:
- O método para calcular o salário foi explicado na secção 5.2.
- Se o parâmetro [SS] não corresponder a nenhum funcionário (a camada [DAO] devolveu um ponteiro nulo), o método lançará uma [PamException] com um código de erro apropriado.
5.10.4. Testar a camada [business]
Criamos dois programas de teste:
![]() |
As classes de teste [3] são criadas no pacote [metier] [2] dentro do ramo [Test Packages] [1] do projeto.
A classe [JUnitMetier_1] poderia ter o seguinte aspeto:
Não existem asserções Assert.assertCondition na classe. Estamos simplesmente a tentar calcular alguns salários para que possamos verificá-los manualmente. A saída no ecrã obtida ao executar a classe anterior é a seguinte:
- Linha 4: Recibo de vencimento de Justine Laverti
- linha 5: recibo de vencimento de Marie Jouveinal
- linha 6: a exceção devido ao facto de o funcionário com o número de segurança social «xx» não existir.
Pergunta: A linha 17 de [JUnitMetier_1] utiliza o bean Spring denominado metier. Forneça a definição deste bean no ficheiro [spring-config-metier-dao.xml].
A classe [JUnitMetier_2] poderia ser a seguinte:
A classe [JUnitMetier_2] é uma cópia da classe [JUnitMetier_1], com a diferença de que, desta vez, as asserções foram colocadas no método test01.
Pergunta: Escreva o método test01.
Ao executar a classe [JUnitMetier_2], obtêm-se os seguintes resultados se tudo correr bem:

5.11. A camada [ui] da aplicação [PAM] – versão console
Agora que a camada [business] foi escrita, ainda precisamos de escrever a camada [ui] [1]:
![]() |
Iremos criar duas implementações diferentes da camada [ui]: uma versão de consola e uma versão GUI Swing:
![]() |
5.11.1. A classe [ ui.console.Main]
Vamos primeiro concentrar-nos na aplicação de consola implementada pela classe [ui.console.Main] acima. O seu funcionamento foi descrito na Secção 5.3. O esqueleto da classe [Main] poderia ser o seguinte:
Pergunta: Complete o código acima.
5.11.2. Execução
Para executar a classe [ui.console.Main], proceda da seguinte forma:
![]() |
- Em [1], selecione as propriedades do projeto,
- em [2], selecione a propriedade [Executar] do projeto,
- utilize o botão [3] para especificar a classe (conhecida como classe principal) a executar,
- selecione a classe [4],
- A classe aparece em [5]. Esta classe requer três argumentos para ser executada (número de segurança social, número de horas trabalhadas, número de dias trabalhados). Estes argumentos são introduzidos em [6],
- assim que isso estiver feito, o projeto pode ser executado [7]. A configuração anterior significa que a classe [ui.console.Main] será executada.
Os resultados da execução são apresentados na janela [output]:
![]() | ![]() |
5.12. A camada [ui] da aplicação [PAM] – versão gráfica
Vamos agora implementar a camada [ui] com uma interface gráfica de utilizador:
![]() |
![]() |
- em [1], a classe [PamJFrame] da interface gráfica
- em [2]: a interface gráfica do utilizador
5.12.1. Um tutorial rápido
Para criar a interface gráfica do utilizador, proceda da seguinte forma:
![]() |
- [1]: Crie um novo ficheiro utilizando o botão [1] [Novo ficheiro...]
- [2]: Selecione a categoria de ficheiro [Formulários GUI Swing], ou seja, formulários gráficos
- [3]: Selecione o tipo [Formulário JFrame], um tipo de formulário vazio
![]() |
- [5]: Dê um nome ao formulário; este será também o nome da classe
- [6]: Coloque o formulário num pacote
- [8]: O formulário é adicionado à árvore do projeto
- [9]: O formulário é acessível através de duas vistas: [Design] [9], que permite projetar os vários componentes do formulário, e [Source] [10 abaixo], que fornece acesso ao código Java do formulário. Em última análise, um formulário é uma classe Java como qualquer outra. A vista [Design] é uma ferramenta para projetar o formulário. Sempre que um componente é adicionado no modo [Design], o código Java é adicionado na vista [Source] para o refletir.
![]() |
- [11]: A lista de componentes Swing disponíveis para um formulário pode ser encontrada na janela [Paleta].
- [12]: A janela [Inspector] apresenta a estrutura em árvore dos componentes do formulário. Os componentes com representação visual encontram-se no ramo [JFrame]; os restantes, no ramo [Outros Componentes].
![]() |
- Em [13], selecionamos um componente [JLabel] com um único clique
- Em [14], arrastamo-lo para o formulário no modo [Design]
- Em [15], definimos as propriedades do JLabel (texto, tipo de letra).
![]() |
- Em [16], o resultado.
- Em [17], solicitamos uma pré-visualização 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 vista [Source] do formulário, foi adicionado código Java para gerir o JLabel adicionado.
Está disponível um tutorial sobre a criação de formulários com o NetBeans no URL [http://www.netbeans.org/kb/trails/matisse.html].
5.12.2. A GUI [PamJFrame]
Vamos criar a seguinte interface gráfica do utilizador:
![]() |
- em [1], a interface gráfica do utilizador
- em [2], a estrutura em árvore dos seus componentes: um JLabel e seis contentores JPanel
JLabel1
![]() |
JPanel1
![]() | ![]() |
JPanel2
![]() | ![]() |
JPanel3
![]() | ![]() |
JPanel4
![]() | ![]() |
JPanel5
![]() | ![]() |
Exercício prático: Crie a interface gráfica anterior utilizando o tutorial [http://www.netbeans.org/kb/trails/matisse.html].
5.12.3. Eventos da Interface Gráfica do Utilizador
Leitura recomendada: o capítulo [Interfaces gráficas de utilizador] em [ref2].
Vamos tratar do clique no botão [jButtonSalaire]. Para criar o método que trata deste evento, podemos proceder da seguinte forma:
![]() |
O manipulador para o clique no botão [JButtonSalaire] é gerado:
O código Java que associa o método anterior ao clique no botão [JButtonSalaire] também é gerado:
As linhas 2–5 especificam que o clique (evt do tipo ActionPerformed) no botão [jButtonSalaire] (linha 2) deve ser tratado pelo método [jButtonSalaireActionPerformed] (linha 4).
Também iremos tratar do evento [caretUpdate] (movimento do cursor) no campo de entrada [jTextFieldHT]. Para criar o manipulador para este evento, procedemos como anteriormente:
![]() |
O manipulador para o evento [caretUpdate] no campo de entrada [jTextFieldHT] é gerado:
O código Java que vincula o método anterior ao evento [caretUpdate] no campo de texto [jTextFieldHT] também é gerado:
As linhas 1–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 GUI
Voltemos à arquitetura da nossa aplicação:
![]() |
A camada [ui] necessita de uma referência à camada [business]. Vamos recordar como essa referência foi obtida na aplicação de consola:
O método é o mesmo na aplicação GUI. Quando a aplicação GUI é inicializada, a referência [IMetier metier] da linha 3 acima também deve ser inicializada. O código gerado para a GUI é atualmente o seguinte:
- Linhas 29–35: o método estático [main] que inicia a aplicação
- linha 32: é criada e tornada visível uma instância da GUI [PamJFrame].
- linhas 7-9: o construtor da GUI.
- linha 8: chamada ao método [initComponents] definido na linha 17. Este método é gerado automaticamente com base no trabalho realizado no modo [Design]. Não o modifique.
- linha 21: o método que irá tratar do deslocamento do cursor de entrada no campo [jTextFieldHT]
- Linha 25: O método que irá tratar do clique no botão [jButtonSalaire]
Para adicionar as nossas próprias inicializações ao código anterior, podemos proceder da seguinte forma:
- Linha 4: Chamamos um método personalizado para realizar as nossas próprias inicializações. Estas são definidas pelo código nas linhas 10–42
Pergunta: Usando os comentários como guia, complete o código para o procedimento [doMyInit].
5.12.5. Manipuladores de eventos
Pergunta: Escreva o método [jTextFieldHTCaretUpdate]. Este método deve garantir que, se os dados no campo [jTextFieldHT] não forem um número real >=0, o botão [jButtonSalaire] seja desativado.
Pergunta: Escreva o método [jButtonSalaireActionPerformed], que deve exibir o recibo de vencimento do funcionário selecionado em [jComboBoxEmployes].
5.12.6. Executar a GUI
Para executar a GUI, modifique a configuração [Run] do projeto:
![]() |
- Em [1], introduza a classe da GUI
O projeto deve estar completo com os seus ficheiros de configuração (persistence.xml, spring-config-metier-dao.xml) e a classe GUI. Inicie o SGBD de destino antes de executar o projeto.
5.13. Implementação da camada JPA com o EclipseLink
Estamos interessados na seguinte arquitetura, na qual a camada JPA é agora implementada pelo EclipseLink:
![]() |
5.13.1. O projeto NetBeans
O novo projeto NetBeans é criado através da cópia do projeto anterior:
![]() |
- em [1]: após clicar com o botão direito do rato no projeto Hibernate, selecione Copiar
- usando o botão [2], selecione a pasta pai para o 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. Tem o mesmo nome que o original,
- em [2] e [3], renomeie-o para [mv-pam-spring-eclipselink].
O projeto deve ser modificado em dois locais para o adaptar à nova camada JPA / EclipseLink:
- em [4], os ficheiros de configuração do Spring devem ser modificados. É aqui que se encontra a configuração da camada JPA.
- Em [5], as bibliotecas do projeto devem ser modificadas: as bibliotecas do Hibernate devem ser substituídas pelas do EclipseLink.
Comecemos por este último ponto. O ficheiro [pom.xml] para o novo projeto será o seguinte:
<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: dependências para a implementação JPA do EclipseLink,
- linhas 19–24: o repositório Maven para o EclipseLink.
Os ficheiros de configuração do Spring devem ser modificados para indicar que a implementação do JPA foi alterada. Em ambos os ficheiros, apenas a secção que configura a camada JPA é alterada. Por exemplo, no [spring-config-metier-dao.xml] temos:
As linhas 19–36 configuram a camada JPA. A implementação JPA utilizada é o Hibernate (linha 22). Além disso, a base de dados de destino é [dbpam_hibernate] (linha 41).
Para mudar para uma implementação JPA/EclipseLink, as linhas 19–35 acima são substituídas pelas linhas abaixo:
- Linha 5: A implementação JPA utilizada é o EclipseLink
- linha 9: a propriedade databasePlatform define o SGBD de destino, neste caso o MySQL
- linha 11: para gerar as tabelas da base de dados quando a camada JPA é instanciada. Aqui, a propriedade está comentada.
- linha 7: para exibir as instruções SQL emitidas pela camada JPA na consola. Aqui, a propriedade está comentada.
Além disso, a base de dados de destino passa a ser [dbpam_eclipselink] (linha 4 abaixo):
5.13.2. Executar os testes
Antes de testar toda a aplicação, é aconselhável verificar se os testes JUnit são bem-sucedidos com a nova implementação do JPA. Antes de os executar, começaremos por eliminar as tabelas da base de dados. Para tal, no separador [Runtime] do NetBeans, se necessário, crie uma ligação à base de dados dbpam_eclipselink / MySQL5. Uma vez ligado à base de dados dbpam_eclipselink / MySQL5, pode prosseguir com a eliminação das tabelas, conforme ilustrado abaixo:
- [1]: antes da eliminação
- [2]: após a eliminação
![]() |
Depois de fazer isto, pode executar o primeiro teste na camada [DAO]: InitDB, que preenche a base de dados. Para garantir que as tabelas anteriormente eliminadas são recriadas pela aplicação, certifique-se de que, na configuração do Spring JPA / EclipseLink, a linha:
exista e não esteja comentada.
Compilamos o projeto e, em seguida, executamos o teste [JUnit InitDB]:
![]() |
- Em [1], o teste InitDB é executado.
- Em [2], o teste falha. A exceção é lançada pelo Spring e não por um teste que falhou.
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 com o agente Java para utilizar o InstrumentationLoadTimeWeaver. Consulte a documentação do Spring.
O Spring indica que existe um problema de configuração. A mensagem não é clara. A razão para a exceção foi explicada na secção 3.1.9 de [ref1]. Para que a configuração do Spring/EclipseLink funcione, a JVM que executa a aplicação deve ser iniciada com um parâmetro específico, um agente Java. O formato deste parâmetro é o seguinte:
[spring-agent.jar] é o agente Java necessário à JVM para gerir a configuração do Spring/EclipseLink.
Ao executar um projeto, é possível passar argumentos para a JVM:
![]() |
- Em [1], pode aceder às propriedades do projeto
- Em [2], as propriedades de execução
- Em [3], passe o parâmetro -javaagent para a JVM
5.13.3. InitDB
Agora estamos prontos para testar [InitDB] novamente. Desta vez, os resultados são os seguintes:
![]() |
- Em [1], o teste foi bem-sucedido
- Em [2], no separador [Serviços], atualizamos a ligação do NetBeans à base de dados [dbpam_eclipselink]
- Em [3], foram criadas quatro tabelas
![]() |
- em [5], visualizamos o conteúdo da tabela [employees]
- em [6], o resultado.
5.13.4. JUnitDao
A execução da classe de teste [JUnitDao] pode falhar, mesmo que tenha sido bem-sucedida com a implementação JPA/Hibernate. Para compreender o motivo, vamos analisar um exemplo.
O método que está a ser testado é o seguinte método IndemniteDao.create:
- linhas 15–22: o método que está a ser testado
O método de teste é o seguinte:
package dao;
...
public class JUnitDao {
// layers DAO
static private IEmployeDao employeDao;
static private IIndemniteDao indemniteDao;
static private ICotisationDao cotisationDao;
@BeforeClass
public static void init() {
// log
log("init");
// application configuration
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-DAO.xml");
// layers DAO
employeDao = (IEmployeDao) ctx.getBean("employeDao");
indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
}
@Before()
public void clean() {
// empty the base
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);
}
// tests
….
@Test
public void test05() {
log("test05");
// we create two allowances with the same index
// violates index uniqueness constraint
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;
// checks
Assert.assertEquals(31, ex.getCode());
} catch (Throwable th1) {
th = th1;
}
// checks
Assert.assertTrue(erreur);
// exception chain
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());
}
// the 1st allowance had to be continued
Indemnite indemnite = indemniteDao.find(indemnite1.getId());
// check
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);
// the second indemnity should not have persisted
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 utilizando uma camada JPA/Hibernate são os seguintes:
O teste é bem-sucedido, o que significa que as asserções foram verificadas e nenhuma exceção foi lançada pelo método de teste.
Pergunta: Explique o que aconteceu.
Os resultados obtidos com uma camada JPA/EclipseLink são os seguintes:
Tal como no Hibernate anteriormente, o teste é bem-sucedido, o que significa que as asserções são verificadas e nenhuma exceção é lançada pelo método de teste.
Pergunta: Explique o que aconteceu.
Pergunta: A partir destes dois exemplos, o que podemos concluir sobre a intercambiabilidade das implementações JPA? É total neste caso?
5.13.5. Os outros testes
Depois de a camada [DAO] ter sido testada e considerada correta, podemos passar a testar a camada [business] e o próprio projeto na sua versão de consola ou gráfica. Alterar a implementação do JPA não tem efeito nas camadas [business] e [UI]; portanto, se estas camadas funcionavam com o Hibernate, funcionarão com o EclipseLink, com algumas exceções: o exemplo anterior mostra que as exceções lançadas pelas camadas [DAO] podem diferir. Assim, no caso do teste, o Spring / JPA / Hibernate lança uma [PamException], uma exceção específica da aplicação [pam], enquanto o Spring / JPA / EclipseLink lança uma [TransactionSystemException], uma exceção da estrutura Spring. Se, no caso de teste, a camada [ui] esperar uma [PamException] porque foi construída com o Hibernate, ela deixará de funcionar ao mudar para o EclipseLink.
5.13.6. Trabalho a realizar
Tarefa prática: Voltar a testar as aplicações de consola e Swing com diferentes SGBDs: MySQL5, Oracle XE, SQL Server.





















































































