Skip to content

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

Concluderemo il nostro percorso di apprendimento su Struts con un caso di studio. Per garantire un approccio realistico, l’esempio che analizzeremo sarà decisamente più complesso rispetto a quelli esaminati in precedenza. Per i principianti, è senza dubbio preferibile approfondire le basi di Struts 2 con applicazioni personali prima di affrontare questo caso di studio.

L’applicazione utilizzerà un’architettura a livelli:

L'insieme dei livelli [metier], [dao], [jpa/hibernate] ci verrà fornito sotto forma di un archivio jar di cui descriveremo in dettaglio le funzionalità. L’integrazione dei livelli sarà gestita da Spring. Il livello [web] sarà implementato da Struts 2.

18.1. Il problema

Ci proponiamo di scrivere un’applicazione web che consenta di redigere la busta paga delle assistenti materne impiegate presso la «Maison de la petite enfance» di un comune.

Image

Questo caso di studio è presentato nel documento:

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

In questo documento, il caso di studio è implementato con la seguente architettura a più livelli:

Il livello [web] è implementato utilizzando il framework JSF (Java Server Faces). Adotteremo questa stessa architettura implementando il livello [web] con Struts 2. Per illustrare i vantaggi delle architetture a livelli, utilizzeremo l’archivio jar dei livelli [metier, dao, jpa] della versione JSF e lo collegheremo a un livello [web / struts2]:

Presenteremo i seguenti elementi del livello [metier, dao, jpa]:

  • il livello [web] si interfaccia con il livello [métier]. Presenteremo tale interfaccia.
  • il livello [jpa] accede a un database. Lo presenteremo.
  • Il livello [jpa] trasforma le righe delle tabelle del database in entità JPA gestite da tutti i livelli dell’applicazione. Le presenteremo.
  • I livelli [metier, dao, jpa] vengono istanziati da Spring. Presenteremo il file di configurazione che realizza tale istanziazione e integrazione.

18.2. Il database

Utilizzeremo il seguente database MySQL [dbpam_hibernate]:

Image

  • In [1], il database presenta tre tabelle:
    • [employes]: una tabella che registra le dipendenti di un asilo nido
    • [cotisations]: una tabella che registra le aliquote dei contributi sociali
    • [indemnites]: una tabella che registra le informazioni necessarie per calcolare la busta paga delle dipendenti

Tabella [employes]

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

Il contenuto della tabella potrebbe essere il seguente:

 

Tabella [cotisations]

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

Il contenuto della tabella potrebbe essere il seguente:

 

Tabella [indemnites]

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

Il contenuto della tabella potrebbe essere il seguente:

 

L’esportazione della struttura del database in un file SQL fornisce il seguente risultato:

#
# Struttura della tabella `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;

#
# Struttura della tabella `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;

#
# Struttura della tabella `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. Le 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 delle tabelle lette dal database vengono trasformate in oggetti denominati entità JPA. Viceversa, in fase di scrittura, le entità JPA vengono trasformate in righe delle tabelle. Queste entità vengono gestite da tutti i livelli, in particolare dal livello web. È quindi necessario conoscerle:

L'entità [Employe] rappresenta una riga della tabella [Employes]

ID
chiave primaria di tipo autoincrement
VERSION
numero di versione del record
PRENOM
nome della dipendente
NOM
il suo cognome
ADRESSE
il suo indirizzo
CP
il suo codice postale
VILLE
la sua città
INDEMNITE_ID
chiave esterna su INDEMNITES(ID)

L'entità [Employe] è 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()
    +"]";
  }
  
  // getter e setter
   ...  
}

Si ignoreranno le annotazioni @ destinate al livello [Jpa]. I diversi campi della classe riflettono le diverse colonne della tabella [EMPLOYES]. Il campo «indemnites» (riga 29) riflette il fatto che la tabella [EMPLOYES] ha una chiave esterna sulla tabella [INDEMNITES]. Quando si modifica un dipendente, si modificano anche le sue indennità.

L’entità [Indemnite] è l’espressione oggetto di una riga della tabella [INDEMNITES]:

ID
chiave primaria di tipo autoincrement
VERSION
numero di versione del record
BASE_HEURE
costo in euro di un'ora di guardia
ENTRETIEN_JOUR
indennità in euro per giorno di guardia
REPAS_JOUR
indennità pasto in euro per giorno di servizio
INDEMNITES_CP
indennità per ferie retribuite. Si tratta di una percentuale
da applicare allo stipendio base.

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()
    + "]";
  }
  
   // getter e setter
....  
}

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

L'entità [Cotisation] è l'espressione oggetto di una riga della tabella [COTISATIONS]:

ID
chiave primaria di tipo autoincrement
VERSION
numero di versione del record
SECU
aliquota (percentuale) dei contributi previdenziali
sociale
RETRAITE
aliquota contributiva per la pensione
CSGD
aliquota contributiva per il contributo sociale
generale deducibile
CSGRDS
aliquota contributiva per il contributo sociale
generale e il contributo al rimborso
del debito sociale

L'entità [Cotisation] è 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()+"]";
  }
  
  // getter e setter
  ... 
}

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

18.4. Modalità di calcolo dello stipendio di una tata

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

  • l’indice della dipendente
  • il numero di giorni lavorati
  • il numero di ore lavorate

Ecco uno screenshot del calcolo di uno stipendio:

Image

Presentiamo ora il metodo di calcolo dello stipendio mensile di una tata. Non si tratta di quello effettivamente utilizzato nella realtà. Prendiamo ad esempio lo stipendio della signora Marie Jouveinal, che ha lavorato 150 ore in 20 giorni durante il mese di riferimento.

Vengono presi in considerazione i seguenti elementi:
[TOTALHEURES]: total des heures
 travaillées dans le mois
[TOTALJOURS]: total des jours
 travaillés dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
Lo stipendio base dell'assistente
è dato dalla formula
seguente:
[SALAIREBASE]=([TOTALHEURES]
*[BASEHEURE])*(1+[INDEMNITESCP]/
100)
[SALAIREBASE]=(150*[2.1])
*(1+0.15)= 362,25
Una certa quota di contributi sociali
devono essere trattenute da questo stipendio
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
Totale dei contributi sociali:
[COTISATIONSSOCIALES]=[SALAIREBASE]
*(CSGRDS+CSGD+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
Inoltre, l'assistente all'infanzia ha diritto,
per ogni giorno lavorato, a un'indennità
di mantenimento e a un'indennità di
pasti. A tal fine riceve le indennità
seguenti:
[Indemnités]=[TOTALJOURS]
*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
In definitiva, lo stipendio netto da corrispondere alla tata è il seguente:
[SALAIREBASE]-
[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

18.5. L'interfaccia del livello [métier]

Torniamo all’architettura dell’applicazione che stiamo realizzando:

Il livello [web / struts 2] comunica con l’interfaccia del livello [métier]. Quest’ultima è la seguente:


package metier;

import java.util.List;
import jpa.Employe;

public interface IMetier {
  // ottenere la busta paga
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // elenco dei dipendenti
  List<Employe> findAllEmployes();
}
  • riga 8: il metodo che ci consentirà di calcolare lo stipendio di un dipendente
  • riga 10: il metodo che ci consentirà di compilare il menu a tendina dei dipendenti

Il metodo calculerFeuillesalaire restituisce un'istanza della classe [FeuilleSalaire] come segue:


package metier;

import java.io.Serializable;
import jpa.Cotisation;
import jpa.Employe;

public class FeuilleSalaire implements Serializable {
  // campi privati

  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

  // costruttori
  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 + "]";
  }

  // getter e setter
  ...
}

Il foglio paga racchiude le seguenti informazioni:

  • riga 10: informazioni sul dipendente di cui si calcola lo stipendio
  • riga 11: le diverse aliquote contributive
  • riga 12: elementi della retribuzione

La classe [ElementsSalaire] è la seguente:


package metier;

import java.io.Serializable;

public class ElementsSalaire implements Serializable{
  
  // campi privati
  private double salaireBase;
  private double cotisationsSociales;
  private double indemnitesEntretien;
  private double indemnitesRepas;
  private double salaireNet;
  
  // costruttori
  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 + "]";
  }
  
  // getter e setter
  ...
}
  • righe 8-12: le componenti della retribuzione

18.6. Il file di configurazione di Spring

L'integrazione dei livelli [métier, dao, jpa] è garantita 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">
  
  <!-- livelli applicativi -->
 
<!-- logica di 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" />
  
  <!-- configurazione 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>
  
  <!-- la fonte dei dati 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>
  
  <!-- gestore delle transazioni -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>
  
  <!-- traduzione delle eccezioni -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
  
  <!-- persistenza -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
  
</beans>

Non cercheremo di spiegare questa configurazione. È necessaria per l’istanziazione e l’integrazione dei livelli [métier, dao, jpa]. La nostra applicazione web, che si baserà su questi livelli, dovrà quindi riprendere questa configurazione. Si noti che le righe 32-37 configurano le caratteristiche JDBC del database. Il lettore che desiderasse cambiare database dovrà modificare queste righe.

Per ulteriori informazioni su questa configurazione, si rimanda al documento «Introduzione a Java EE 5» disponibile su URL [http://tahe.developpez.com/java/javaee].