Skip to content

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

Wir werden unsere Einführung in Struts mit einer Fallstudie abschließen. Um realitätsnah zu sein, wird das untersuchte Beispiel deutlich komplexer sein als die zuvor behandelten. Für Anfänger ist es sicherlich ratsam, die Grundlagen von Struts 2 zunächst anhand eigener Anwendungen zu vertiefen, bevor sie sich dieser Fallstudie zuwenden.

Die Anwendung wird eine Schichtenarchitektur verwenden:

Die Schichten [metier], [dao] und [jpa/hibernate] werden uns in Form eines JAR-Archivs zur Verfügung gestellt, dessen Funktionen wir im Detail erläutern werden. Die Integration der Schichten erfolgt über Spring. Die Schicht [web] wird mit Struts 2 implementiert.

18.1. Das Problem

Wir beabsichtigen, eine Webanwendung zu entwickeln, mit der die Gehaltsabrechnungen der von der „Maison de la petite enfance“ einer Gemeinde beschäftigten Tagesmütter erstellt werden können.

Image

Diese Fallstudie wird im folgenden Dokument vorgestellt:

Einführung in Java EE 5, verfügbar unter der URL [http://tahe.developpez.com/java/javaee]

In diesem Dokument wird die Fallstudie mit der folgenden mehrschichtigen Architektur implementiert:

Die Schicht [web] wird mithilfe des Frameworks JSF (Java Server Faces) implementiert. Wir werden dieselbe Architektur verwenden und dabei die Schicht [web] mit Struts 2 implementieren. Um den Nutzen von Schichtarchitekturen zu veranschaulichen, werden wir das JAR-Archiv der Schichten [metier, dao, jpa] aus der Version JSF verwenden und es mit einer Schicht [web / struts2] verbinden:

Wir werden die folgenden Elemente der Schichten [metier, dao, jpa] vorstellen:

  • Die Schicht [web] kommuniziert mit der Schnittstelle der Schicht [métier]. Wir werden diese Schnittstelle vorstellen.
  • Die Schicht [jpa] greift auf eine Datenbank zu. Wir werden sie vorstellen.
  • Die Schicht [jpa] wandelt die Zeilen der Datenbanktabellen in JPA-Entitäten um, die von allen Schichten der Anwendung verarbeitet werden. Wir werden diese vorstellen.
  • Die Schichten [metier, dao, jpa] werden von Spring instanziiert. Wir werden die Konfigurationsdatei vorstellen, die diese Instanziierung und Integration vornimmt.

18.2. Die Datenbank

Wir werden die folgende Datenbank MySQL [dbpam_hibernate] verwenden:

Image

 
  • In [1] enthält die Datenbank drei Tabellen:
  • [employes]: eine Tabelle, in der die Mitarbeiterinnen einer Kindertagesstätte erfasst sind
  • [cotisations]: eine Tabelle, in der die Sozialversicherungsbeiträge gespeichert sind
  • [indemnites]: eine Tabelle, in der Informationen zur Berechnung der Löhne der Mitarbeiterinnen gespeichert sind

Tabelle [employes]

  • in [2] die Mitarbeitertabelle und in [3] die Bedeutung ihrer Felder

Der Inhalt der Tabelle könnte wie folgt aussehen:

Image

Tabelle [cotisations]

  • in [4] die Beitragstabelle und in [5] die Bedeutung ihrer Felder

Der Inhalt der Tabelle könnte wie folgt aussehen:

Image

Tabelle [indemnites]

  • in [6] die Tabelle der Entschädigungen und in [7] die Bedeutung ihrer Felder

Der Inhalt der Tabelle könnte wie folgt aussehen:

Image

Der Export der Datenbankstruktur in eine Datei SQL liefert folgendes Ergebnis:

#
# Struktur der Tabelle `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;

#
# Struktur der Tabelle `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;

#
# Struktur der Tabelle `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. Die Entitäten JPA

In der folgenden Architektur

Die Schicht [Jpa] fungiert als Brücke zwischen den von der Schicht [dao] verarbeiteten Objekten und den vom JDBC-Treiber verarbeiteten Zeilen der Datenbanktabellen. Die aus der Datenbank gelesenen Tabellenzeilen werden in Objekte umgewandelt, die als JPA-Entitäten bezeichnet werden. Umgekehrt werden beim Schreiben die JPA-Entitäten in Tabellenzeilen umgewandelt. Diese Entitäten werden von allen Schichten und insbesondere von der Webschicht verarbeitet. Wir müssen sie daher kennen:

Die Entität [Employe] stellt einen Datensatz der Tabelle [Employes] dar

Image

ID – Primärschlüssel vom Typ „Autoincrement“VERSION – Versionsnummer des DatensatzesPRENOM – Vorname der MitarbeiterinNOM – ihr NachnameADRESSE – ihre AdresseCP – ihre PostleitzahlVILLE – ihre StadtINDEMNITE_ID – Fremdschlüssel auf INDEMNITES(ID)

Die Entität [Employe] lautet wie folgt:


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

Die Anmerkungen @, die für die Ebene [Jpa] bestimmt sind, werden ignoriert. Die verschiedenen Felder der Klasse spiegeln die verschiedenen Spalten der Tabelle [EMPLOYES] wider. Das Feld „indemnites“ (Zeile 29) spiegelt wider, dass die Tabelle [EMPLOYES] einen Fremdschlüssel auf die Tabelle [INDEMNITES] besitzt. Bei der Bearbeitung eines Mitarbeiters werden auch dessen Zulagen bearbeitet.

Die Entität [Indemnite] ist der Objektausdruck einer Zeile der Tabelle [INDEMNITES]:

Image

ID – Primärschlüssel vom Typ „Autoinkrement“ VERSIONnVersionsnummer des Datensatzes BASE_HEURE Kosten in Euro für eine Stunde Bereitschaftsdienst ENTRETIEN_JOUR Vergütung in Euro pro Tag Bereitschaftsdienst REPAS_JOUR Verpflegungszuschuss in Euro pro Tag Bereitschaftsdienst INDEMNITES_CP Vergütungen für bezahlten Urlaub. Es handelt sich um einen Prozentsatz, der auf das Grundgehalt anzuwenden ist.

Die Entität [Indemnite] lautet wie folgt:


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

Die verschiedenen Felder der Klasse spiegeln die verschiedenen Spalten der Tabelle [INDEMNITES] wider.

Die Entität [Cotisation] ist der Objektausdruck einer Zeile der Tabelle [COTISATIONS]:

Image

ID Primärschlüssel vom Typ „Autoinkrement“ VERSION Versionsnummer des Datensatzes SECU Beitragssatz (Prozent) des Sozialversicherungsbeitrags RETRAITE: Beitragssatz für die Altersvorsorge CSGD: Beitragssatz für den abzugsfähigen allgemeinen Sozialbeitrag CSGRD: Beitragssatz für den allgemeinen Sozialbeitrag und den Beitrag zur Tilgung der Sozialschuld

Die Entität [Cotisation] lautet wie folgt:


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

Die verschiedenen Felder der Klasse spiegeln die verschiedenen Spalten der Tabelle [INDEMNITES] wider.

18.4. Berechnungsmethode für das Gehalt einer Tagesmutter

Die Webanwendung, die wir erstellen werden, ermöglicht es uns, das Gehalt einer Mitarbeiterin anhand von drei Angaben zu berechnen:

  • dem Index der Mitarbeiterin
  • die Anzahl der gearbeiteten Tage
  • die Anzahl der gearbeiteten Stunden

Hier ist ein Screenshot der Gehaltsberechnung:

Image

Wir stellen nun die Berechnungsmethode für das Monatsgehalt einer Tagesmutter vor. Es handelt sich dabei nicht um die in der Praxis verwendete Methode. Als Beispiel nehmen wir das Gehalt von Frau Marie Jouveinal, die im Abrechnungsmonat 150 Stunden an 20 Tagen gearbeitet hat.

Folgende Faktoren werden berücksichtigt:

[TOTALHEURES]: total des heures travaillées dans le mois

[TOTALJOURS]: total des jours travaillés dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
Das Grundgehalt der Erzieherin
ergibt sich aus der folgenden Formel
:

[SALAIREBASE]=([TOTALHEURES]*[BASEHEURE])*(1+[INDEMNITESCP]/100)
[SALAIREBASE]=(150*[2.1])*(1+0.15)= 362,25
Von diesem Gehalt
müssen von diesem Grundgehalt
abgezogen werden:

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
Gesamtsumme der Sozialbeiträge:

[COTISATIONSSOCIALES]=[SALAIREBASE]*(CSGRDS+CSGD+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
Darüber hinaus hat die Tagesmutter
für jeden gearbeiteten Tag auf eine
sowie auf eine Verpflegungszulage
Mahlzeitenpauschale. In diesem Zusammenhang erhält sie die
:

[Indemnités]=[TOTALJOURS]*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
Letztendlich beträgt das an die Tagesmutter zu zahlende Nettogehalt:

[SALAIREBASE]-[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

18.5. Die Schnittstelle der Schicht [métier]

Kommen wir noch einmal auf die Architektur der Anwendung zurück, die wir entwickeln:

Die Schicht [web / struts 2] kommuniziert mit der Schnittstelle der Schicht [métier]. Diese sieht wie folgt aus:


package metier;

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

public interface IMetier {
  // Lohnabrechnung abrufen
  FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
  // Mitarbeiterliste
  List<Employe> findAllEmployes();
}
  • Zeile 8: die Methode, mit der wir das Gehalt eines Mitarbeiters berechnen können
  • Zeile 10: die Methode, mit der wir das Mitarbeiter-Auswahlfeld ausfüllen können

Die Methode calculerFeuillesalaire gibt eine Instanz der folgenden Klasse [FeuilleSalaire] zurück:


package metier;

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

public class FeuilleSalaire implements Serializable {
  // Private Felder

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

  // Konstruktoren
  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 und Setter
  ...
}

Der Lohnzettel enthält folgende Informationen:

  • Zeile 10: Informationen über den Mitarbeiter, dessen Gehalt berechnet wird
  • Zeile 11: die verschiedenen Beitragssätze
  • Zeile 12: Lohnbestandteile

Die Klasse [ElementsSalaire] lautet wie folgt:


package metier;

import java.io.Serializable;

public class ElementsSalaire implements Serializable{
  
  // private Felder
  private double salaireBase;
  private double cotisationsSociales;
  private double indemnitesEntretien;
  private double indemnitesRepas;
  private double salaireNet;
  
  // Konstruktoren
  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 und Setter
  ...
}
  • Zeilen 8–12: Lohnbestandteile

18.6. Die Spring-Konfigurationsdatei

Die Integration der Schichten [métier, dao, jpa] wird durch die folgende Spring-Konfigurationsdatei gewährleistet:


<?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">
  
  <!-- Anwendungsschichten -->
 
<!-- Geschäftslogik -->
  <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" />
  
  <!-- Konfiguration 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>
  
  <!-- Datenquelle 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>
  
  <!-- Transaktionsmanager -->
  <tx:annotation-driven transaction-manager="txManager" />
  <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
  </bean>
  
  <!-- Ausnahmeübersetzung -->
  <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
  
  <!-- Persistenz -->
  <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
  
</beans>

Wir werden nicht versuchen, diese Konfiguration zu erklären. Sie ist für die Instanziierung und Integration der Schichten [métier, dao, jpa] erforderlich. Unsere Webanwendung, die auf diesen Schichten aufbauen wird, muss daher diese Konfiguration übernehmen. Es sei darauf hingewiesen, dass die Zeilen 32–37 die JDBC-Eigenschaften der Datenbank konfigurieren. Leser, die die Datenbank wechseln möchten, müssen diese Zeilen ändern.

Weitere Informationen zu dieser Konfiguration finden Sie im Dokument „Einführung in Java EE 5“, das unter der URL [http://tahe.developpez.com/java/javaee] verfügbar ist.