Skip to content

18. Caso di studio: Struts 2 / Tiles / Spring / Hibernate / MySQL

Concluderemo la nostra introduzione a Struts con un caso di studio. Per essere realistici, l'esempio che esamineremo sarà significativamente più complesso di quelli trattati in precedenza. Per i principianti, è probabilmente meglio consolidare la comprensione delle basi di Struts 2 attraverso progetti personali prima di affrontare questo caso di studio.

L'applicazione utilizzerà un'architettura a livelli:

I livelli [business], [DAO] e [JPA/Hibernate] ci saranno forniti sotto forma di un archivio JAR, di cui descriveremo in dettaglio le caratteristiche. Spring gestirà l'integrazione di questi livelli. Il livello [web] sarà implementato utilizzando Struts 2.

18.1. Il problema

Proponiamo di sviluppare un'applicazione web per generare le buste paga degli operatori dell'assistenza all'infanzia impiegati presso il “Centro per la prima infanzia” di un comune.

Image

Questo caso di studio è presentato nel documento:

Introduzione a Java EE 5 disponibile all'URL [http://tahe.developpez.com/java/javaee]

In questo documento, il caso di studio è implementato utilizzando la seguente architettura multilivello:

Il livello [web] è implementato utilizzando il framework JSF (Java Server Faces). Adotteremo questa stessa architettura implementando il livello [web] con Struts 2. Per dimostrare i vantaggi delle architetture a livelli, utilizzeremo l'archivio JAR contenente i livelli [business, DAO, JPA] della versione JSF e lo collegheremo a un livello [web / Struts 2]:

Presenteremo i seguenti componenti dei livelli [business, DAO, JPA]:

  • Il livello [web] si interfaccia con il livello [business]. Presenteremo questa interfaccia.
  • Il livello [JPA] accede a un database. Lo presenteremo.
  • Il livello [JPA] trasforma le righe delle tabelle del database in entità JPA utilizzate da tutti i livelli dell'applicazione. Le presenteremo.
  • I livelli [business, DAO, JPA] vengono istanziati da Spring. Presenteremo il file di configurazione che esegue questa istanziazione e integrazione.

18.2. Il database

Utilizzeremo il seguente database MySQL [dbpam_hibernate]:

Image

 
  • In [1], il database contiene tre tabelle:
  • [employees]: una tabella che registra i dipendenti di un asilo nido
  • [contributions]: una tabella che memorizza le aliquote dei contributi previdenziali
  • [indemnites]: una tabella che contiene le informazioni utilizzate per calcolare la retribuzione dei dipendenti

Tabella [dipendenti]

  • In [2], la tabella dei dipendenti, e in [3], il significato dei suoi campi

Il contenuto della tabella potrebbe essere il seguente:

Image

Tabella [contributions]

  • In [4], la tabella dei contributi, e in [5], il significato dei suoi campi

Il contenuto della tabella potrebbe essere il seguente:

Image

Tabella [indemnites]

  • in [6], la tabella delle indennità, e in [7], il significato dei suoi campi

Il contenuto della tabella potrebbe essere il seguente:

Image

L'esportazione della struttura del database in un file SQL produce il seguente risultato:

#
# Structure for the `cotisations` table :
#

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;

#
# Structure for the `indemnites` table :
#

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;

#
# Structure for the `employes` table :
#

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. Entità JPA

Nella seguente architettura

Il livello [Jpa] funge da ponte tra gli oggetti gestiti dal livello [dao] e le righe delle tabelle del database gestite dal driver Jdbc. Le righe lette dalle tabelle del database vengono trasformate in oggetti chiamati entità Jpa. Al contrario, durante la scrittura, le entità JPA vengono convertite in righe di tabella. Queste entità sono gestite da tutti i livelli, in particolare dal livello web. È quindi necessario comprenderle:

L'entità [Employee] rappresenta una riga nella tabella [Employees]

ID: chiave primaria di tipo autoincrementVERSION: numero di versione del recordFIRST_NAME: nome del dipendenteLAST_NAME: cognome del dipendenteADDRESS: indirizzo del dipendenteZIP: codice postale del dipendenteCITY: città del dipendenteINDEMNITY_ID: chiave esterna su INDEMNITIES(ID)

Image

L'entità [Employee] è la seguente:


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

Ignoreremo le annotazioni @ destinate al livello [Jpa]. I vari campi della classe riflettono le varie colonne della tabella [EMPLOYEES]. Il campo indemnities (riga 29) riflette il fatto che la tabella [EMPLOYEES] ha una chiave esterna sulla tabella [INDEMNITIES]. Quando modifichiamo un dipendente, modifichiamo anche le sue indennità.

L'entità [Indemnite] è la rappresentazione a oggetti di una riga nella tabella [INDEMNITES]:

ID: chiave primaria autoincrementale; VERSION: numero di versione del record; BASE_HEURE: costo in euro per un'ora di reperibilità; DAILY_MAINTENANCE: indennità giornaliera in euro; MEAL_DAY: indennità pasto in euro per giorno di assistenza; PAID_LEAVE_ALLOWANCES: indennità per ferie retribuite. Si tratta di una percentuale da applicare allo stipendio base.

Image

L'entità [Indemnite] è la seguente:


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

I vari campi della classe riflettono le varie colonne della tabella [INDEMNITIES].

L'entità [Cotisation] è la rappresentazione oggettuale di una riga della tabella [COTISATIONS]:

ID: chiave primaria di tipo autoincrement VERSION: numero di versione del record SECU: aliquota contributiva previdenziale (percentuale) socialPENSION: aliquota contributiva pensionistica CSGD: aliquota contributiva per la previdenza sociale generale e il rimborso del debito sociale

Image

L'entità [Contribution] è la seguente:


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

I diversi campi della classe riflettono le diverse colonne della tabella [INDEMNITES].

18.4. Metodo basato su [INDEMNITES] per il calcolo dello stipendio di un operatore dell'assistenza all'infanzia

L'applicazione web che scriveremo ci permetterà di calcolare lo stipendio di un dipendente sulla base di tre informazioni:

  • l'indice del dipendente
  • il numero di giorni lavorati
  • il numero di ore lavorate

Ecco uno screenshot di un calcolo dello stipendio:

Image

Vi presenteremo ora il metodo per calcolare lo stipendio mensile di un operatore dell'assistenza all'infanzia. Questo non è inteso come il metodo da utilizzare nella vita reale. Come esempio, useremo lo stipendio della signora Marie Jouveinal, che ha lavorato 150 ore in 20 giorni durante il periodo di paga.

Vengono presi in considerazione i seguenti fattori:

[TOTALHOURS]: totale delle ore lavorate nel mese

[TOTALDAYS]: totale dei giorni lavorati nel mese
[TOTALHOURS]=150
[TOTALDAYS] = 20
Lo stipendio base del
è dato dalla seguente formula
:
[STIPENDIOBASE]=([TOTALEORE]*[TARIFFAORARIA])*(1+[INDENNITÀCP]/100)
[STIPENDIOBASE]=(150*[2,1])*(1+0,15)= 362,25
Da questo stipendio base
deve essere detratto da questo stipendio base
:

Contributo sociale generale e contributo al rimborso del debito sociale: [STIPENDIOBASE]*[CSGRDS/100]

Contributo sociale generale deducibile: [STIPENDIOBASE]*[CSGD/100]

Previdenza sociale, pensione di reversibilità, pensione di vecchiaia: [STIPENDIO BASE]*[SECU/100]

Pensione integrativa + AGPF + Assicurazione contro la disoccupazione: [STIPENDIO BASE]*[PENSIONE/100]
CSGRDS: 12,64
CSGD: 22,28
Previdenza sociale: 34,02
Pensione: 28,55
Totale contributi previdenziali:
[CONTRIBUTI SOCIALI] = [STIPENDIO BASE] * (CSGRDS + CSGD + SECU + PENSIONE) / 100
[CONTRIBUTI SOCIALI] = 97,48
Inoltre, l’assistente all’infanzia ha diritto,
per ogni giorno lavorato, a un
nonché a un'indennità pasto
. Pertanto, riceve il
:
[Indennità] = [TOTALGIORNI] * (MANUTENZIONEGIORNALIERA + PASTOGIORNALIERO)
[INDENNITÀ]=104
Alla fine, lo stipendio netto da versare alla tata è il seguente:
[STIPENDIO BASE] - [CONTRIBUTI PREVIDENZIALI] + [INDENNITÀ]
[STIPENDIO NETTO]=368,77

18.5. L'interfaccia del livello [business]

Diamo un'occhiata all'architettura dell'applicazione che stiamo realizzando:

Il livello [web / Struts 2] comunica con l'interfaccia del livello [business]. Il funzionamento è il seguente:


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();
}
  • riga 8: il metodo che ci permetterà di calcolare lo stipendio di un dipendente
  • riga 10: il metodo che ci permetterà di popolare il menu a tendina dei dipendenti

Il metodo calculatePaystub restituisce un'istanza della seguente 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
  ...
}

La busta paga contiene le seguenti informazioni:

  • riga 10: informazioni sul dipendente di cui si sta calcolando lo stipendio
  • riga 11: le varie aliquote contributive
  • riga 12: componenti salariali

La classe [ElementsSalaire] è la seguente:


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
  ...
}
  • righe 8-12: componenti dello stipendio

18.6. Il file di configurazione Spring

L'integrazione dei livelli [business, DAO, JPA] è gestita dal seguente file di configurazione 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>

Non tenteremo di spiegare questa configurazione. È necessaria per l'istanziazione e l'integrazione dei livelli [business, DAO, JPA]. La nostra applicazione web, che si baserà su questi livelli, deve quindi adottare questa configurazione. Si noti che le righe 32– –37 configurano le proprietà JDBC del database. I lettori che desiderano cambiare il database devono modificare queste righe.

Per ulteriori informazioni su questa configurazione, consultare il documento Introduzione a Java EE 5 disponibile all'URL [http://tahe.developpez.com/java/javaee].