18. Estudo de caso: Struts 2 / Tiles / Spring / Hibernate / MySQL
Concluiremos a nossa introdução ao Struts com um estudo de caso. Para ser realista, o exemplo que iremos analisar será significativamente mais complexo do que os abordados anteriormente. Para principiantes, é provavelmente melhor consolidar a compreensão dos conceitos básicos do Struts 2 através de projetos pessoais antes de abordar este estudo de caso.
A aplicação utilizará uma arquitetura em camadas:
![]() |
As camadas [business], [DAO] e [JPA/Hibernate] serão-nos fornecidas sob a forma de um arquivo JAR, cujas funcionalidades iremos detalhar. O Spring irá tratar da integração destas camadas. A camada [web] será implementada utilizando o Struts 2.
![]() |
18.1. O Problema
Propomos criar uma aplicação web para gerar recibos de vencimento para prestadores de cuidados infantis empregados pelo «Centro de Primeira Infância» de um município.

Este estudo de caso é apresentado no documento:
Introdução ao Java EE 5, disponível no URL [http://tahe.developpez.com/java/javaee]
Neste documento, o estudo de caso é implementado utilizando a seguinte arquitetura multicamadas:
![]() |
A camada [web] é implementada utilizando o framework JSF (Java Server Faces). Iremos adotar esta mesma arquitetura, implementando a camada [web] com o Struts 2. Para demonstrar as vantagens das arquiteturas em camadas, utilizaremos o arquivo JAR que contém as camadas [business, DAO, JPA] da versão JSF e ligá-lo-emos a uma camada [web / Struts 2]:
![]() |
Apresentaremos os seguintes componentes das camadas [negócio, DAO, JPA]:
- A camada [web] faz a interface com a camada [business]. Apresentaremos esta interface.
- A camada [JPA] acede a uma base de dados. Iremos apresentá-la.
- A camada [JPA] transforma as linhas das tabelas da base de dados em entidades JPA que são utilizadas por todas as camadas da aplicação. Iremos apresentá-las.
- As camadas [negócio, DAO, JPA] são instanciadas pelo Spring. Apresentaremos o ficheiro de configuração que realiza esta instanciação e integração.
18.2. A Base de Dados
Iremos utilizar a seguinte base de dados MySQL [dbpam_hibernate]:

- Em [1], a base de dados tem três tabelas:
- [employees]: uma tabela que regista os funcionários de uma creche
- [contributions]: uma tabela que armazena as taxas de contribuição para a segurança social
- [indemnizações]: uma tabela que armazena informações utilizadas para calcular o salário dos funcionários
tabela [employees]
![]() |
- Em [2], a tabela de funcionários, e em [3], o significado dos seus campos
O conteúdo da tabela poderia ser o seguinte:
![]()
Tabela [contribuições]
![]() |
- Em [4], a tabela de contribuições, e em [5], o significado dos seus campos
O conteúdo da tabela poderia ser o seguinte:
![]()
Tabela [indemnites]
![]() |
- em [6], a tabela de subsídios, e em [7], o significado dos seus campos
O conteúdo da tabela poderia ser o seguinte:
![]()
A exportação da estrutura da base de dados para um ficheiro SQL produz o seguinte resultado:
18.3. Entidades JPA
Na seguinte arquitetura
![]() |
A camada [Jpa] atua como uma ponte entre os objetos tratados pela camada [dao] e as linhas nas tabelas da base de dados tratadas pelo controlador Jdbc. As linhas lidas das tabelas da base de dados são transformadas em objetos chamados entidades JPA. Por outro lado, durante a gravação, as entidades JPA são convertidas em linhas de tabela. Estas entidades são tratadas por todas as camadas, particularmente pela camada web. Por isso, precisamos de as compreender:
A entidade [Employee] representa uma linha na tabela [Employees]
ID: chave primária do tipo autoincremento; VERSION: número de versão do registo; FIRST_NAME: nome próprio do funcionário; LAST_NAME: apelido do funcionário; ADDRESS: morada do funcionário; ZIP: código postal do funcionário; CITY: cidade do funcionário; INDEMNITY_ID: chave estrangeira na tabela INDEMNITIES (ID)

A entidade [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
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
public Employe() {
}
public Employe(String SS, String nom, String prenom, String adresse, String ville, String codePostal, Indemnite indemnite){
setSS(SS);
setNom(nom);
setPrenom(prenom);
setAdresse(adresse);
setVille(ville);
setCodePostal(codePostal);
setIndemnite(indemnite);
}
@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()
+"]";
}
// getters and setters
...
}
Iremos ignorar as anotações @ destinadas à camada [Jpa]. Os vários campos da classe refletem as várias colunas da tabela [EMPLOYEES]. O campo indemnities (linha 29) reflete o facto de a tabela [EMPLOYEES] ter uma chave estrangeira na tabela [INDEMNITIES]. Quando manipulamos um funcionário, também manipulamos as suas indemnizações.
A entidade [Indemnite] é a representação objeto de uma linha na tabela [INDEMNITIES]:
ID: chave primária auto-incrementada; VERSION: número da versão do registo; BASE_HEURE: custo em euros por uma hora de serviço de plantão; DAILY_MAINTENANCE: subsídio diário em euros; MEAL_DAY: subsídio de refeição em euros por dia de assistência; PAID_LEAVE_ALLOWANCES: subsídios de férias pagas. Trata-se de uma percentagem a aplicar ao salário base.

A entidade [Indemnite] é a seguinte:
package jpa;
...
@Entity
@Table(name="INDEMNITES")
public class Indemnite implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
@Column(name="VERSION",nullable=false)
private int version;
@Column(name="INDICE", nullable=false,unique=true)
private int indice;
@Column(name="BASE_HEURE",nullable=false)
private double baseHeure;
@Column(name="ENTRETIEN_JOUR",nullable=false)
private double entretienJour;
@Column(name="REPAS_JOUR",nullable=false)
private double repasJour;
@Column(name="INDEMNITES_CP",nullable=false)
private double indemnitesCP;
public Indemnite() {
}
public Indemnite(int indice, double baseHeure, double entretienJour, double repasJour, double indemnitesCP){
setIndice(indice);
setBaseHeure(baseHeure);
setEntretienJour(entretienJour);
setRepasJour(repasJour);
setIndemnitesCP(indemnitesCP);
}
@Override
public String toString() {
return "jpa.Indemnite[id=" + getId()
+ ",version="+getVersion()
+",indice="+getIndice()
+",base heure="+getBaseHeure()
+",entretien jour"+getEntretienJour()
+",repas jour="+getRepasJour()
+",indemnités CP="+getIndemnitesCP()
+ "]";
}
// getters and setters
....
}
Os vários campos da classe refletem as várias colunas da tabela [INDEMNITIES].
A entidade [Cotisation] é a representação objetiva de uma linha na tabela [COTISATIONS]:
ID: chave primária do tipo autoincremento; VERSION: número da versão do registo; SECU: taxa de contribuição para a segurança social (percentagem); socialPENSION: taxa de contribuição para a pensão; CSGD: taxa de contribuição para a dedução social geral; CSGRDS: taxa de contribuição para a dívida social e para o reembolso

A entidade [Contribution] é a seguinte:
package jpa;
...
@Entity
@Table(name="COTISATIONS")
public class Cotisation implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
@Column(name="VERSION",nullable=false)
private int version;
@Column(name="CSGRDS",nullable=false)
private double csgrds;
@Column(name="CSGD",nullable=false)
private double csgd;
@Column(name="SECU",nullable=false)
private double secu;
@Column(name="RETRAITE",nullable=false)
private double retraite;
public Cotisation() {
}
public Cotisation(double csgrds, double csgd, double secu, double retraite){
setCsgrds(csgrds);
setCsgd(csgd);
setSecu(secu);
setRetraite(retraite);
}
@Override
public String toString() {
return "jpa.Cotisation[id=" + getId() + ",version=" + getVersion()+",csgrds="+getCsgrds()+"" +
",csgd="+getCsgd()+",secu="+getSecu()+",retraite="+getRetraite()+"]";
}
// getters and setters
...
}
Os diferentes campos da classe refletem as diferentes colunas da tabela [INDEMNITES].
18.4. Método baseado em [INDEMNITES] para calcular o salário de um prestador de cuidados infantis
A aplicação web que vamos criar permitirá calcular o salário de um funcionário com base em três informações:
- o índice do funcionário
- o número de dias trabalhados
- o número de horas trabalhadas
Aqui está uma captura de ecrã de um cálculo salarial:

Vamos agora apresentar o método de cálculo do salário mensal de uma prestadora de cuidados infantis. Este método não se destina a ser utilizado na vida real. Como 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 é dado 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, o prestador de cuidados infantis tem direito, por cada dia trabalhado, a um bem como a um subsídio de refeição . Como tal, recebe o : | ||
No final, o salário líquido a pagar à prestadora de cuidados infantis é o seguinte: |
18.5. A interface da camada [business]
Vamos dar uma olhada na arquitetura da aplicação que estamos a construir:
![]() |
A camada [web / Struts 2] comunica com a interface da camada [business]. Isto funciona da seguinte forma:
package metier;
import java.util.List;
import jpa.Employe;
public interface IMetier {
// get your payslip
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// list of employees
List<Employe> findAllEmployes();
}
- linha 8: o método que nos permitirá calcular o salário de um funcionário
- linha 10: o método que nos permitirá preencher a lista suspensa de funcionários
O método calculatePaystub devolve uma instância da seguinte classe [Paystub]:
package metier;
import java.io.Serializable;
import jpa.Cotisation;
import jpa.Employe;
public class FeuilleSalaire implements Serializable {
// private fields
private Employe employe;
private Cotisation cotisation;
private ElementsSalaire elementsSalaire;
// manufacturers
public FeuilleSalaire() {
}
public FeuilleSalaire(Employe employe, Cotisation cotisation,
ElementsSalaire elementsSalaire) {
setEmploye(employe);
setCotisation(cotisation);
setElementsSalaire(elementsSalaire);
}
// toString
@Override
public String toString() {
return "[" + employe + "," + cotisation + ","
+ elementsSalaire + "]";
}
// getters and setters
...
}
O recibo de vencimento contém as seguintes informações:
- linha 10: informações sobre o trabalhador cujo salário está a ser calculado
- linha 11: as várias taxas de contribuição
- linha 12: componentes salariais
A classe [ElementsSalaire] é a seguinte:
package metier;
import java.io.Serializable;
public class ElementsSalaire implements Serializable{
// private fields
private double salaireBase;
private double cotisationsSociales;
private double indemnitesEntretien;
private double indemnitesRepas;
private double salaireNet;
// manufacturers
public ElementsSalaire() {
}
public ElementsSalaire(double salaireBase, double cotisationsSociales,
double indemnitesEntretien, double indemnitesRepas,
double salaireNet) {
setSalaireBase(salaireBase);
setCotisationsSociales(cotisationsSociales);
setIndemnitesEntretien(indemnitesEntretien);
setIndemnitesRepas(indemnitesRepas);
setSalaireNet(salaireNet);
}
// toString
@Override
public String toString() {
return "[salaire base=" + salaireBase + ",cotisations sociales=" + cotisationsSociales + ",indemnités d'entretien="
+ indemnitesEntretien + ",indemnités de repas=" + indemnitesRepas + ",salaire net="
+ salaireNet + "]";
}
// getters and setters
...
}
- linhas 8-12: componentes salariais
18.6. O ficheiro de configuração do Spring
A integração das camadas [negócio, DAO, JPA] é gerida pelo seguinte ficheiro de configuração Spring:
<?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">
<!-- application layers -->
<!-- business -->
<bean id="metier" class="metier.Metier">
<property name="employeDao" ref="employeDao"/>
<property name="cotisationDao" ref="cotisationDao"/>
</bean>
<!-- dao -->
<bean id="employeDao" class="dao.EmployeDao" />
<bean id="indemniteDao" class="dao.IndemniteDao" />
<bean id="cotisationDao" class="dao.CotisationDao" />
<!-- configuration 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="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<!-- the DBCP data source -->
<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>
<!-- transaction manager -->
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- translation of exceptions -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- persistence -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>
Não tentaremos explicar esta configuração. Ela é necessária para a instanciação e integração das camadas [negócio, DAO, JPA]. A nossa aplicação web, que dependerá destas camadas, deve, portanto, adotar esta configuração. Note-se que as linhas 32–37 configuram as propriedades JDBC da base de dados. Os leitores que desejarem alterar a base de dados devem modificar estas linhas.
Para mais informações sobre esta configuração, consulte o documento Introdução ao Java EE 5 disponível no URL [http://tahe.developpez.com/java/javaee].








