Skip to content

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.

Image

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

Image

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

#
# Estrutura da tabela `cotisations`: 
#

CREATE TABLE `cotisations` (
  `ID` bigint(20) NOT NULL auto_increment,
  `SECU` double NOT NULL,
  `RETRAITE` double NOT NULL,
  `CSGD` double NOT NULL,
  `CSGRDS` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

#
# Estrutura da tabela `indemnites`: 
#

CREATE TABLE `indemnites` (
  `ID` bigint(20) NOT NULL auto_increment,
  `ENTRETIEN_JOUR` double NOT NULL,
  `REPAS_JOUR` double NOT NULL,
  `INDICE` int(11) NOT NULL,
  `INDEMNITES_CP` double NOT NULL,
  `BASE_HEURE` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `INDICE` (`INDICE`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

#
# Estrutura da tabela `employes`: 
#

CREATE TABLE `employes` (
  `ID` bigint(20) NOT NULL auto_increment,
  `PRENOM` varchar(20) NOT NULL,
  `SS` varchar(15) NOT NULL,
  `ADRESSE` varchar(50) NOT NULL,
  `CP` varchar(5) NOT NULL,
  `VILLE` varchar(30) NOT NULL,
  `NOM` varchar(30) NOT NULL,
  `VERSION` int(11) NOT NULL,
  `INDEMNITE_ID` bigint(20) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `SS` (`SS`),
  KEY `FK_EMPLOYES_INDEMNITE_ID` (`INDEMNITE_ID`),
  CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES `indemnites` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;

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]

ID
chave primária do tipo autoincremento
VERSION
número da versão do registro
PRENOM
nome da funcionária
NOM
sobrenome
ADRESSE
seu endereço
CP
seu CEP
VILLE
sua cidade
INDEMNITE_ID
chave estrangeira em INDEMNITES (ID)

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

ID
chave primária do tipo autoincremento
VERSION
número da versão do registro
BASE_HEURE
custo em euros de uma hora de plantão
ENTRETIEN_JOUR
Indenização em euros por dia de plantão
REPAS_JOUR
Subsídio de refeição em euros por dia de plantão
INDEMNITES_CP
Subsídios de férias remuneradas. Trata-se de uma porcentagem
a ser aplicado 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 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]:

ID
chave primária do tipo autoincremento
VERSION
número da versão do registro
SECU
taxa (porcentagem) de contribuição para a previdência
social
RETRAITE
taxa de contribuição para a aposentadoria
CSGD
taxa de contribuição para a contribuição social
geral dedutível
CSGRDS
taxa de contribuição para a contribuição social
geralizada e a contribuição para o reembolso
da dívida social

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:

Image

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

Os seguintes elementos são levados em consideração:
[TOTALHEURES]: total des heures
 travaillées dans le mois
[TOTALJOURS]: total des jours
 travaillés dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
O salário-base da assistente
é dado pela fórmula
a seguir:
[SALAIREBASE]=([TOTALHEURES]
*[BASEHEURE])*(1+[INDEMNITESCP]/
100)
[SALAIREBASE]=(150*[2.1])
*(1+0.15)= 362,25
Uma determinada quantia de contribuições sociais
devem ser deduzidas desse salário
base:
Contribution sociale généralisée
 et contribution au remboursement
 de la dette sociale :
 [SALAIREBASE]*[CSGRDS/100]
Contribution sociale généralisée
 déductible :
 [SALAIREBASE]*[CSGD/100]
Sécurité sociale, veuvage,
 vieillesse :
 [SALAIREBASE]*[SECU/100]
Retraite Complémentaire + AGPF + 
Assurance Chômage : [SALAIREBASE]*[RETRAITE/100]
CSGRDS : 12,64
CSGD : 22,28
Sécurité sociale : 34,02
Retraite : 28,55
Total das contribuições sociais:
[COTISATIONSSOCIALES]=[SALAIREBASE]
*(CSGRDS+CSGD+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
Além disso, a babá tem direito,
por cada dia trabalhado, a um auxílio
de manutenção, bem como a um auxílio-alimentação
refeição. A esse título, ela recebe as indenizações
seguintes:
[Indemnités]=[TOTALJOURS]
*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
No final, o salário líquido a ser pago à babá é o seguinte:
[SALAIREBASE]-
[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

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