Introduction à STRUTS2 par l'exemple


précédentsommairesuivant

XVIII. Etude de cas : Struts 2 / Tiles / Spring / Hibernate / MySQL

Nous allons terminer notre apprentissage de Struts par une étude de cas. Afin d'être réaliste, l'exemple étudié va être nettement plus complexe que ceux étudiés précédemment. Pour les débutants, il est sans doute préférable d'approfondir les bases de Struts 2 avec des applications personnelles avant d'aborder cette étude de cas.

L'application va utiliser une architecture à couches :

Image non disponible

L'ensemble des couches [metier], [dao], [jpa/hibernate] nous sera fourni sous la forme d'une archive jar dont nous détaillerons les fonctionnalités. L'intégration des couches sera assurée par Spring. La couche [web] sera implémentée par Struts 2.

Image non disponible

XVIII-A. Le problème

On se propose d'écrire une application web permettant d'établir le bulletin de salaire des assistantes maternelles employées par la "Maison de la petite enfance" d'une commune.

Image non disponible

Cette étude de cas est présentée dans le document :

Introduction à Java EE 5 disponible à l'Url [http://tahe.developpez.com/java/javaee]

Dans ce document, l'étude de cas est implémentée avec l'architecture multi-couches suivante :

Image non disponible

La couche [web] est implémentée à l'aide du framework JSF (Java Server Faces). Nous allons prendre cette même architecture en implémentant la couche [web] avec Struts 2. Pour montrer l'intérêt des architectures en couches, nous allons utiliser l'archive jar des couches [metier, dao, jpa] de la version JSF et la connecter à une couche [web / struts2] :

Image non disponible

Nous présenterons les éléments suivants des couches [metier, dao, jpa] :

  • la couche [web] s'adresse à l'interface de la couche [métier]. Nous présenterons cette interface.
  • la couche [jpa] accède à une base de données. Nous la présenterons.
  • la couche [jpa] transforme les lignes des tables de la base de données en entités Jpa manipulées par toutes les couches de l'application. Nous les présenterons.
  • les couches [metier, dao, jpa] sont instanciées par Spring. Nous présenterons le fichier de configuration qui réalise cette instanciation et cette intégration.

XVIII-B. La base de données

Nous utiliserons la base de données MySQL [dbpam_hibernate] suivante :

Image non disponible

  • en [1], la base a trois tables :
  • [employes] : une table qui enregistre les employées d'une crèche
  • [cotisations] : une table qui enregistre des taux de cotisations sociales
  • [indemnites] : une table qui enregistre des informations permettant de calculer la paie des employées

Table [employes]

Image non disponible

  • en [2], la table des employés et en [3], la signification de ses champs

Le contenu de la table pourrait être le suivant :

Image non disponible

Table [cotisations]

Image non disponible

  • en [4], la table des cotisations et en [5], la signification de ses champs

Le contenu de la table pourrait être le suivant :

Image non disponible

Table [indemnites]

Image non disponible

  • en [6], la table des indemnités et en [7], la signification de ses champs

Le contenu de la table pourrait être le suivant :

Image non disponible

L'exportation de la structure de la base vers un fichier SQL donne le résultat suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
#
# 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;

XVIII-C. Les entités Jpa

Dans l'architecture suivante

Image non disponible

La couche [Jpa] joue le rôle de pont entre les objets manipulés par la couche [dao] et les lignes des tables de la base de données manipulées par le pilote Jdbc. Les lignes des tables lues dans la base de données sont transformées en objets appelées entités Jpa. Inversement en écriture, les entités Jpa sont transformées en lignes dans les tables. Ces entités sont manipulées par toutes les couches et notamment par la couche web. Il nous faut donc les connaître :

L'entité [Employe] représente une ligne de la table [Employes]

Image non disponible

L'entité [Employe] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
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 et setters
   ...  
}

On ignorera les annotations @ destinées à la couche [Jpa]. Les différents champs de la classe reflètent les différentes colonnes de la table [EMPLOYES]. Le champ indemnites (ligne 29) reflète le fait que la table [EMPLOYES] a une clé étrangère sur la table [INDEMNITES]. Lorsqu'on manipule un employé, on manipule aussi ses indemnités.

L'entité [Indemnite] est l'expression objet d'une ligne de la table [INDEMNITES] :

Image non disponible

L'entité [Indemnite] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
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 et setters
....  
}

Les différents champs de la classe reflètent les différentes colonnes de la table [INDEMNITES].

L'entité [Cotisation] est l'expression objet d'une ligne de la table [COTISATIONS] :

Image non disponible

L'entité [Cotisation] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
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 et setters
  ... 
}

Les différents champs de la classe reflètent les différentes colonnes de la table [INDEMNITES].

XVIII-D. Mode de calcul du salaire d'une assistante maternelle

L'application web que nous allons écrire va nous permettre de calculer le salaire d'une employée à partir de trois informations :

  • l'indice de l'employée
  • le nombre de jours travaillés
  • le nombre d'heures travaillées

Voici une copie d'écran de calcul d'un salaire :

Image non disponible

Nous présentons maintenant le mode de calcul du salaire mensuel d'une assistante maternelle. Il ne prétend pas être celui utilisé dans la réalité. Nous prenons pour exemple, le salaire de Mme Marie Jouveinal qui a travaillé 150 h sur 20 jours pendant le mois à payer.

Les éléments suivants sont pris en compte : [TOTALHEURES]: total des heures travaillées dans le mois
[TOTALJOURS]: total des jours travaillés dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
Le salaire de base de l'assistante maternelle est donné par la formule suivante : [SALAIREBASE]=([TOTALHEURES]*[BASEHEURE])*(1+[INDEMNITESCP]/100) [SALAIREBASE]=(150*[2.1])*(1+0.15)= 362,25
Un certain nombre de cotisations sociales doivent être prélevées sur ce salaire de 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 des cotisations sociales : [COTISATIONSSOCIALES]=[SALAIREBASE]*(CSGRDS+CSGD+SECU+RETRAITE)/100 [COTISATIONSSOCIALES]=97,48
Par ailleurs, l'assistante maternelle a droit, chaque jour travaillé, à une indemnité d'entretien ainsi qu'à une indemnité de repas. A ce titre elle reçoit les indemnités suivantes : [Indemnités]=[TOTALJOURS]*(ENTRETIENJOUR+REPASJOUR) [INDEMNITES]=104
Au final, le salaire net à payer à l'assistante maternelle est le suivant : [SALAIREBASE]-[COTISATIONSSOCIALES]+[INDEMNITÉS] [salaire NET]=368,77

XVIII-E. L'interface de la couche [métier]

Revenons sur l'architecture de l'application que nous construisons :

Image non disponible

La couche [web / struts 2] communique avec l'interface de la couche [métier]. Celle-ci est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
package metier;

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

public interface IMetier {
  // obtenir la feuille de salaire
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // liste des employés
  List<Employe> findAllEmployes();
}
  • ligne 8 : la méthode qui nous permettra de calculer le salaire d'un employé
  • ligne 10 : la méthode qui nous permettra de remplir le combo des employés

La méthode calculerFeuillesalaire rend une instance de la classe [FeuilleSalaire] suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
package metier;

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

public class FeuilleSalaire implements Serializable {
  // champs privés

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

  // constructeurs
  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 et setters
  ...
}

La feuille de salaire encapsule les informations suivantes :

  • ligne 10 : des informations sur l'employé dont on calcule le salaire
  • ligne 11 : les différents taux de cotisations
  • ligne 12 : des éléments du salaire

La classe [ElementsSalaire] est la suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
package metier;

import java.io.Serializable;

public class ElementsSalaire implements Serializable{
  
  // champs privés
  private double salaireBase;
  private double cotisationsSociales;
  private double indemnitesEntretien;
  private double indemnitesRepas;
  private double salaireNet;
  
  // constructeurs
  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 et setters
  ...
}
  • lignes 8-12 : les éléments du salaire

XVIII-F. Le fichier de configuration de Spring

L'intégration des couches [métier, dao, jpa] est assurée par le fichier de configuration Spring suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
<?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">
  
  <!-- couches applicatives -->
 
 <!-- métier -->
  <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>
  
  <!-- la source de données 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>
  
  <!-- le gestionnaire de transactions -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>
  
  <!-- traduction des exceptions -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
  
  <!-- persistence -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
  
</beans>

Nous n'essaierons pas d'expliquer cette configuration. Elle est nécessaire pour l'instanciation et l'intégration des couches [métier, dao, jpa]. Notre application web qui va s'appuyer sur ces couches devra donc reprendre cette configuration. On notera que les lignes 32-37 configurent les caractéristiques Jdbc de la base de données. Le lecteur qui voudrait changer de base de données doit modifier ces lignes.

Pour plus d'informations sur cette configuration, on lira le document Introduction à Java EE 5 disponible à l'Url [http://tahe.developpez.com/java/javaee].


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2012 Serge Tahe. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.