Skip to content

18. Casestudy: Struts 2 / Tiles / Spring / Hibernate / MySQL

We sluiten onze Struts-cursus af met een casestudy. Om het realistisch te houden, zal het voorbeeld dat we behandelen aanzienlijk complexer zijn dan de voorgaande voorbeelden. Voor beginners is het waarschijnlijk beter om de basis van Struts 2 eerst grondig te bestuderen aan de hand van eigen applicaties, voordat ze aan deze casestudy beginnen.

De applicatie maakt gebruik van een gelaagde architectuur:

De lagen [metier], [dao] en [jpa/hibernate] worden ons aangeleverd in de vorm van een jar-archief, waarvan we de functionaliteiten in detail zullen bespreken. De integratie van de lagen wordt verzorgd door Spring. De laag [web] wordt geïmplementeerd door Struts 2.

18.1. Het probleem

We willen een webapplicatie schrijven waarmee de loonstrook kan worden opgesteld voor de kinderopvangmedewerkers die in dienst zijn bij het „Maison de la petite enfance“ van een gemeente.

Image

Deze casestudy wordt gepresenteerd in het document:

Inleiding tot Java EE 5, beschikbaar op URL [http://tahe.developpez.com/java/javaee]

In dit document is de casestudy geïmplementeerd met de volgende meerlaagse architectuur:

De laag [web] is geïmplementeerd met behulp van het framework JSF (Java Server Faces). We zullen dezelfde architectuur hanteren door de laag [web] te implementeren met Struts 2. Om het nut van gelaagde architecturen te illustreren, gaan we het jar-archief van de lagen [metier, dao, jpa] uit versie JSF gebruiken en dit koppelen aan een laag [web / struts2]:

We zullen de volgende elementen van de lagen [metier, dao, jpa] bespreken:

  • de laag [web] is gekoppeld aan de interface van de laag [métier]. We zullen deze interface bespreken.
  • De laag [jpa] heeft toegang tot een database. We zullen deze bespreken.
  • De laag [jpa] zet de rijen uit de databasetabellen om in JPA-entiteiten die door alle lagen van de applicatie worden gebruikt. We zullen deze presenteren.
  • De lagen [metier, dao, jpa] worden door Spring geïnstantieerd. We zullen het configuratiebestand presenteren dat deze instantiëring en integratie uitvoert.

18.2. De database

We zullen de volgende database MySQL [dbpam_hibernate] gebruiken:

Image

  • In [1] bevat de database drie tabellen:
    • [employes]: een tabel waarin de medewerksters van een kinderdagverblijf worden geregistreerd
    • [cotisations]: een tabel waarin de sociale premiepercentages worden vastgelegd
    • [indemnites]: een tabel waarin gegevens worden opgeslagen waarmee het loon van de medewerksters kan worden berekend

Tabel [employes]

  • in [2], de tabel met werknemers en in [3], de betekenis van de velden

De inhoud van de tabel zou als volgt kunnen zijn:

 

Tabel [cotisations]

  • in [4], de tabel met premies en in [5], de betekenis van de velden

De inhoud van de tabel zou als volgt kunnen zijn:

 

Tabel [indemnites]

  • in [6], de tabel met vergoedingen en in [7], de betekenis van de velden

De inhoud van de tabel zou als volgt kunnen zijn:

 

Het exporteren van de databasestructuur naar een bestand SQL levert het volgende resultaat op:

#
# Structuur voor de tabel `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;

#
# Structuur van de tabel `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;

#
# Structuur van de tabel `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. De entiteiten JPA

In de volgende architectuur

De laag [Jpa] fungeert als brug tussen de objecten die door de laag [dao] worden verwerkt en de rijen in de databasetabellen die door de JDBC-driver worden verwerkt. De rijen uit de databasetabellen worden omgezet in objecten die JPA-entiteiten worden genoemd. Omgekeerd, bij het schrijven, worden de JPA-entiteiten omgezet in rijen in de tabellen. Deze entiteiten worden door alle lagen verwerkt, en met name door de weblaag. We moeten ze dus kennen:

De entiteit [Employe] vertegenwoordigt een rij uit de tabel [Employes]

ID
primaire sleutel van het type auto-increment
VERSION
versienummer van het record
PRENOM
voornaam van de werkneemster
NOM
haar achternaam
ADRESSE
haar adres
CP
zijn postcode
VILLE
zijn/haar woonplaats
INDEMNITE_ID
vreemde sleutel op INDEMNITES(ID)

De entiteit [Employe] is als volgt:


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

De @-annotaties die betrekking hebben op de laag [Jpa] worden genegeerd. De verschillende velden van de klasse komen overeen met de verschillende kolommen van de tabel [EMPLOYES]. Het veld ‘indemnites’ (regel 29) geeft aan dat de tabel [EMPLOYES] een vreemde sleutel heeft naar de tabel [INDEMNITES]. Wanneer een werknemer wordt bewerkt, worden ook zijn vergoedingen bewerkt.

De entiteit [Indemnite] is de objectuitdrukking van een rij in de tabel [INDEMNITES]:

ID
primaire sleutel van het type auto-increment
VERSION
versienummer van het record
BASE_HEURE
kosten in euro voor een uur dienst
ENTRETIEN_JOUR
vergoeding in euro per dag dienst
REPAS_JOUR
maaltijdvergoeding in euro per dag dienst
INDEMNITES_CP
vergoedingen voor betaald verlof. Dit is een percentage
dat op het basisloon moet worden toegepast.

De entiteit [Indemnite] is als volgt:


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

De verschillende velden van de klasse komen overeen met de verschillende kolommen van de tabel [INDEMNITES].

De entiteit [Cotisation] is de objectuitdrukking van een rij in de tabel [COTISATIONS]:

ID
primaire sleutel van het type auto-increment
VERSION
versienummer van het record
SECU
premiepercentage voor de sociale
sociale zekerheid
RETRAITE
pensioenpremiepercentage
CSGD
premiepercentage voor de algemene sociale bijdrage
algemene aftrekbare sociale bijdrage
CSGRDS
premiepercentage voor de algemene sociale bijdrage
en de bijdrage voor de aflossing
van de sociale schuld

De entiteit [Cotisation] is als volgt:


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

De verschillende velden van de klasse komen overeen met de verschillende kolommen van de tabel [INDEMNITES].

18.4. Berekeningswijze van het salaris van een kinderoppas

Met de webapplicatie die we gaan schrijven, kunnen we het salaris van een medewerkster berekenen op basis van drie gegevens:

  • de index van de medewerkster
  • het aantal gewerkte dagen
  • het aantal gewerkte uren

Hieronder ziet u een schermafbeelding van een salarisberekening:

Image

We laten nu zien hoe het maandsalaris van een kinderoppas wordt berekend. Dit is niet de berekeningswijze die in de praktijk wordt gebruikt. We nemen als voorbeeld het salaris van mevrouw Marie Jouveinal, die in de te betalen maand 150 uur heeft gewerkt, verdeeld over 20 dagen.

Er wordt rekening gehouden met de volgende elementen:
[TOTALHEURES]: total des heures
 travaillées dans le mois
[TOTALJOURS]: total des jours
 travaillés dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
Het basissalaris van de kleuterleidster
wordt berekend volgens de
:
[SALAIREBASE]=([TOTALHEURES]
*[BASEHEURE])*(1+[INDEMNITESCP]/
100)
[SALAIREBASE]=(150*[2.1])
*(1+0.15)= 362,25
Een aantal sociale premies
moeten worden ingehouden op dit
:
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
Totaal aan sociale premies:
[COTISATIONSSOCIALES]=[SALAIREBASE]
*(CSGRDS+CSGD+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
Bovendien heeft de kinderopvangmoeder recht op
voor elke gewerkte dag op een onderhoudsvergoeding
en een maaltijdvergoeding
maaltijdvergoeding. Hiervoor ontvangt zij de
:
[Indemnités]=[TOTALJOURS]
*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
Uiteindelijk bedraagt het aan de kinderopvangmoeder uit te betalen nettoloon:
[SALAIREBASE]-
[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

18.5. De interface van de laag [métier]

Laten we nog eens terugkomen op de architectuur van de applicatie die we aan het bouwen zijn:

De laag [web / struts 2] communiceert met de interface van de laag [métier]. Deze ziet er als volgt uit:


package metier;

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

public interface IMetier {
  // loonstrook ophalen
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // lijst met werknemers
  List<Employe> findAllEmployes();
}
  • regel 8: de methode waarmee we het salaris van een medewerker kunnen berekenen
  • regel 10: de methode waarmee we de keuzelijst met werknemers kunnen vullen

De methode calculerFeuillesalaire retourneert een instantie van de klasse [FeuilleSalaire], namelijk:


package metier;

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

public class FeuilleSalaire implements Serializable {
  // privévelden

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

  // constructors
  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 en setters
  ...
}

De loonlijst bevat de volgende informatie:

  • regel 10: informatie over de werknemer wiens salaris wordt berekend
  • regel 11: de verschillende premietarieven
  • regel 12: looncomponenten

De klasse [ElementsSalaire] ziet er als volgt uit:


package metier;

import java.io.Serializable;

public class ElementsSalaire implements Serializable{
  
  // privévelden
  private double salaireBase;
  private double cotisationsSociales;
  private double indemnitesEntretien;
  private double indemnitesRepas;
  private double salaireNet;
  
  // constructors
  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 en setters
  ...
}
  • regels 8-12: de looncomponenten

18.6. Het Spring-configuratiebestand

De integratie van de lagen [métier, dao, jpa] wordt verzorgd door het volgende Spring-configuratiebestand:


<?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">
  
  <!-- toepassingslagen -->
 
<!-- bedrijfslogica -->
  <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" />
  
  <!-- configuratie 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>
  
  <!-- de gegevensbron 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>
  
  <!-- de transactiebeheerder -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>
  
  <!-- vertaling van uitzonderingen -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
  
  <!-- persistentie -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
  
</beans>

We zullen deze configuratie niet proberen uit te leggen. Ze is noodzakelijk voor het instantiëren en integreren van de [métier, dao, jpa]-lagen. Onze webapplicatie, die op deze lagen zal steunen, moet deze configuratie dus overnemen. Merk op dat de regels 32-37 de JDBC-instellingen van de database configureren. De lezer die van database wil wisselen, moet deze regels aanpassen.

Voor meer informatie over deze configuratie kunt u het document „Inleiding tot Java EE 5“ raadplegen, dat beschikbaar is op de URL [http://tahe.developpez.com/java/javaee].