18. Estudo de caso: Struts 2 / Tiles / Spring / Hibernate / MySQL
Vamos encerrar nosso aprendizado sobre o Struts com um estudo de caso. Para ser realista, o exemplo analisado será bem mais complexo do que os estudados anteriormente. Para iniciantes, é sem dúvida preferível aprofundar os conceitos básicos do Struts 2 com aplicativos pessoais antes de abordar este estudo de caso.
A aplicação utilizará uma arquitetura em camadas:
![]() |
O conjunto de camadas [metier], [dao], [jpa/hibernate] nos será fornecido na forma de um arquivo JAR, cujas funcionalidades detalharemos. A integração das camadas será feita pelo Spring. A camada [web] será implementada pelo Struts 2.
![]() |
18.1. O problema
Propomos desenvolver um aplicativo web que permita gerar o holerite das babás empregadas pela “Casa da Primeira Infância” de um município.

Este estudo de caso é apresentado no documento:
Introdução ao Java EE 5, disponível em URL [http://tahe.developpez.com/java/javaee]
Neste documento, o estudo de caso é implementado com a seguinte arquitetura em camadas:
![]() |
A camada [web] é implementada com o framework JSF (Java Server Faces). Vamos adotar essa mesma arquitetura ao implementar a camada [web] com o Struts 2. Para demonstrar a vantagem das arquiteturas em camadas, utilizaremos o arquivo jar das camadas [metier, dao, jpa] da versão JSF e o conectaremos a uma camada [web / struts2]:
![]() |
Apresentaremos os seguintes elementos das camadas [metier, dao, jpa]:
- a camada [web] se comunica com a interface da camada [métier]. Apresentaremos essa interface.
- a camada [jpa] acessa um banco de dados. Apresentaremos essa camada.
- A camada [jpa] transforma as linhas das tabelas do banco de dados em entidades JPA manipuladas por todas as camadas da aplicação. Apresentaremos essas entidades.
- As camadas [metier, dao, jpa] são instanciadas pelo Spring. Apresentaremos o arquivo de configuração que realiza essa instanciação e integração.
18.2. O banco de dados
Utilizaremos o seguinte banco de dados MySQL [dbpam_hibernate]:

- No [1], o banco de dados possui três tabelas:
- [employes]: uma tabela que registra as funcionárias de uma creche
- [cotisations]: uma tabela que registra as alíquotas das contribuições sociais
- [indemnites]: uma tabela que registra informações que permitem calcular a folha de pagamento das funcionárias
Tabela [employes]
![]() |
- em [2], a tabela de funcionários, e em [3], o significado de seus campos
O conteúdo da tabela poderia ser o seguinte:
Tabela [cotisations]
![]() |
- em [4], a tabela de contribuições e em [5], o significado de seus campos
O conteúdo da tabela poderia ser o seguinte:
Tabela [indemnites]
![]() |
- em [6], a tabela de indenizações e em [7], o significado de seus campos
O conteúdo da tabela poderia ser o seguinte:
A exportação da estrutura do banco de dados para um arquivo SQL resulta no seguinte:
18.3. As entidades JPA
Na seguinte arquitetura
![]() |
A camada [Jpa] atua como uma ponte entre os objetos manipulados pela camada [dao] e as linhas das tabelas do banco de dados manipuladas pelo driver JDBC. As linhas das tabelas lidas no banco de dados são transformadas em objetos chamados entidades JPA. Por outro lado, na gravação, as entidades JPA são transformadas em linhas nas tabelas. Essas entidades são manipuladas por todas as camadas e, principalmente, pela camada web. Portanto, precisamos conhecê-las:
A entidade [Employe] representa uma linha da tabela [Employes]
![]() |
|
A entidade [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
@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 e setters
...
}
Ignoraremos as anotações @ destinadas à camada [Jpa]. Os diferentes campos da classe refletem as diferentes colunas da tabela [EMPLOYES]. O campo indemnites (linha 29) reflete o fato de que a tabela [EMPLOYES] possui uma chave estrangeira na tabela [INDEMNITES]. Ao manipular um funcionário, também se manipulam seus subsídios.
A entidade [Indemnite] é a expressão-objeto de uma linha da tabela [INDEMNITES]:
![]() |
|
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 e setters
....
}
Os diferentes campos da classe refletem as diferentes colunas da tabela [INDEMNITES].
A entidade [Cotisation] é a expressão que representa uma linha da tabela [COTISATIONS]:
![]() |
|
A entidade [Cotisation] é 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 e setters
...
}
Os diferentes campos da classe refletem as diferentes colunas da tabela [INDEMNITES].
18.4. Modo de cálculo do salário de uma babá
O aplicativo web que vamos desenvolver nos permitirá calcular o salário de uma funcionária a partir de três informações:
- o índice da funcionária
- o número de dias trabalhados
- o número de horas trabalhadas
Aqui está uma captura de tela do cálculo de um salário:

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: | ||
O salário-base da assistente é dado pela fórmula a seguir: | ||
Uma determinada quantia de contribuições sociais devem ser deduzidas desse salário base: | ||
Total das contribuições sociais: | ||
Além disso, a babá tem direito, por cada dia trabalhado, a um auxílio de manutenção, bem como a um auxílio-alimentação refeição. A esse título, ela recebe as indenizações seguintes: | ||
No final, o salário líquido a ser pago à babá é o seguinte: |
18.5. A interface da camada [métier]
Voltemos à arquitetura do aplicativo que estamos desenvolvendo:
![]() |
A camada [web / struts 2] se comunica com a interface da camada [métier]. Esta é a seguinte:
package metier;
import java.util.List;
import jpa.Employe;
public interface IMetier {
// obter a folha de pagamento
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// lista de funcionários
List<Employe> findAllEmployes();
}
- 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 calculerFeuillesalaire retorna uma instância da classe [FeuilleSalaire] a seguir:
package metier;
import java.io.Serializable;
import jpa.Cotisation;
import jpa.Employe;
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
@Override
public String toString() {
return "[" + employe + "," + cotisation + ","
+ elementsSalaire + "]";
}
// getters e setters
...
}
A folha de pagamento contém as seguintes informações:
- linha 10: informações sobre o funcionário cujo salário está sendo calculado
- linha 11: as diferentes alíquotas de contribuição
- linha 12: componentes do salário
A classe [ElementsSalaire] é a seguinte:
package metier;
import java.io.Serializable;
public class ElementsSalaire implements Serializable{
// campos privados
private double salaireBase;
private double cotisationsSociales;
private double indemnitesEntretien;
private double indemnitesRepas;
private double salaireNet;
// construtores
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 e setters
...
}
- linhas 8-12: os componentes do salário
18.6. O arquivo de configuração do Spring
A integração das camadas [métier, dao, jpa] é garantida pelo seguinte arquivo de configuração do 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">
<!-- camadas de aplicação -->
<!-- domínio de negócios -->
<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" />
<!-- 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="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
</property>
</bean>
<!-- 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_hibernate" />
<property name="username" value="root" />
<property name="password" value="" />
</bean>
<!-- gerenciador de transações -->
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- tradução de exceções -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- persistência -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>
Não tentaremos explicar essa configuração. Ela é necessária para a instanciação e integração das camadas [métier, dao, jpa]. Nossa aplicação web, que se baseará nessas camadas, deverá, portanto, adotar essa configuração. Observe-se que as linhas 32 a 37 configuram as características JDBC do banco de dados. O leitor que desejar alterar o banco de dados deverá modificar essas linhas.
Para obter mais informações sobre essa configuração, consulte o documento “Introdução ao Java EE 5”, disponível no URL [http://tahe.developpez.com/java/javaee].











