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.

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:

- 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:
![]()
Tabelle [cotisations]
![]() |
- in [4] die Beitragstabelle und in [5] die Bedeutung ihrer Felder
Der Inhalt der Tabelle könnte wie folgt aussehen:
![]()
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:
![]()
Der Export der Datenbankstruktur in eine Datei SQL liefert folgendes Ergebnis:
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

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]:

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]:

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:

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: | | |
Das Grundgehalt der Erzieherin ergibt sich aus der folgenden Formel : | | |
Von diesem Gehalt müssen von diesem Grundgehalt abgezogen werden: | | |
Gesamtsumme der Sozialbeiträge: | | |
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 : | | |
Letztendlich beträgt das an die Tagesmutter zu zahlende Nettogehalt: | |
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.








