18. Fallstudie: Struts 2 / Tiles / Spring / Hibernate / MySQL
Wir schließen unsere Einführung in Struts mit einer Fallstudie ab. Um realistisch zu bleiben, wird das Beispiel, das wir untersuchen werden, deutlich komplexer sein als die zuvor behandelten. Für Anfänger ist es wahrscheinlich am besten, sich zunächst durch eigene Projekte ein solides Verständnis der Grundlagen von Struts 2 anzueignen, bevor sie sich an diese Fallstudie wagen.
Die Anwendung wird eine mehrschichtige Architektur verwenden:
![]() |
Die Schichten [Business], [DAO] und [JPA/Hibernate] werden uns in Form eines JAR-Archivs zur Verfügung gestellt, dessen Funktionen wir im Detail beschreiben werden. Spring übernimmt die Integration dieser Schichten. Die [Web]-Schicht wird mit Struts 2 implementiert.
![]() |
18.1. Das Problem
Wir schlagen vor, eine Webanwendung zu entwickeln, mit der Gehaltsabrechnungen für Kinderbetreuer erstellt werden können, die im „Early Childhood Center“ einer Gemeinde beschäftigt sind.

Diese Fallstudie wird in dem 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 unter Verwendung der folgenden mehrschichtigen Architektur implementiert:
![]() |
Die [Web]-Schicht wird mithilfe des JSF-Frameworks (Java Server Faces) implementiert. Wir werden dieselbe Architektur übernehmen, indem wir die [Web]-Schicht mit Struts 2 implementieren. Um die Vorteile von Schichtenarchitekturen zu demonstrieren, verwenden wir das JAR-Archiv, das die [Business-, DAO- und JPA]-Schichten aus der JSF-Version enthält, und verbinden es mit einer [Web-/Struts 2]-Schicht:
![]() |
Wir werden die folgenden Komponenten der [Business-, DAO-, JPA]-Schichten vorstellen:
- Die [Web]-Schicht ist mit der [Business]-Schicht verbunden. Wir werden diese Schnittstelle vorstellen.
- Die [JPA]-Schicht greift auf eine Datenbank zu. Wir werden sie vorstellen.
- Die [JPA]-Schicht wandelt Zeilen aus den Datenbanktabellen in JPA-Entitäten um, die von allen Schichten der Anwendung verwendet werden. Wir werden diese vorstellen.
- Die [Business-, DAO- und JPA-]Schichten werden von Spring instanziiert. Wir werden die Konfigurationsdatei vorstellen, die diese Instanziierung und Integration durchführt.
18.2. Die Datenbank
Wir werden die folgende MySQL-Datenbank [dbpam_hibernate] verwenden:

- In [1] enthält die Datenbank drei Tabellen:
- [employees]: eine Tabelle, in der die Mitarbeiter einer Kindertagesstätte erfasst sind
- [contributions]: eine Tabelle, in der die Sozialversicherungsbeiträge gespeichert sind
- [indemnites]: eine Tabelle, in der Informationen zur Berechnung der Mitarbeiterbezüge gespeichert sind
Tabelle [employees]
![]() |
- In [2] die Tabelle „employees“ und in [3] die Bedeutung ihrer Felder
Der Inhalt der Tabelle könnte wie folgt aussehen:
![]()
Tabelle [Beiträge]
![]() |
- In [4] die Tabelle „Beiträge“ und in [5] die Bedeutung ihrer Felder
Der Inhalt der Tabelle könnte wie folgt aussehen:
![]()
Tabelle [indemnites]
![]() |
- in [6] die Zulagentabelle und in [7] die Bedeutung ihrer Felder
Der Inhalt der Tabelle könnte wie folgt aussehen:
![]()
Der Export der Datenbankstruktur in eine SQL-Datei liefert folgendes Ergebnis:
18.3. JPA-Entitäten
In der folgenden Architektur
![]() |
Die [Jpa]-Schicht fungiert als Brücke zwischen den von der [dao]-Schicht verwalteten Objekten und den Zeilen in den Datenbanktabellen, die vom Jdbc-Treiber verwaltet werden. Die aus den Datenbanktabellen gelesenen Zeilen werden in Objekte umgewandelt, die als JPA-Entitäten bezeichnet werden. Umgekehrt werden JPA-Entitäten beim Schreiben in Tabellenzeilen umgewandelt. Diese Entitäten werden von allen Schichten, insbesondere der Webschicht, verarbeitet. Wir müssen sie daher verstehen:
Die [Employee]-Entität repräsentiert eine Zeile in der [Employees]-Tabelle
ID: Primärschlüssel vom Typ „autoincrement“ VERSION: Versionsnummer des Datensatzes FIRST_NAME: Vorname des Mitarbeiters LAST_NAME: Nachname des Mitarbeiters ADDRESS: Adresse des Mitarbeiters ZIP: Postleitzahl des Mitarbeiters CITY: Ort des Mitarbeiters INDEMNITY_ID: Fremdschlüssel auf INDEMNITIES(ID)

Die Entität [Employee] sieht wie folgt aus:
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 and setters
...
}
Wir ignorieren Annotationen @, die für die [Jpa]-Schicht bestimmt sind. Die verschiedenen Felder der Klasse spiegeln die verschiedenen Spalten der Tabelle [EMPLOYEES] wider. Das Feld „indemnities“ (Zeile 29) spiegelt die Tatsache wider, dass die Tabelle [EMPLOYEES] einen Fremdschlüssel auf die Tabelle [INDEMNITIES] hat. Wenn wir einen Mitarbeiter bearbeiten, bearbeiten wir auch dessen Entschädigungen.
Die Entität [Indemnite] ist die Objektdarstellung einer Zeile in der Tabelle [INDEMNITIES]:
ID: Autoinkrementierender Primärschlüssel VERSION: Versionsnummer des Datensatzes BASE_HEURE: Kosten in Euro für eine Stunde Bereitschaftsdienst DAILY_MAINTENANCE: Tagespauschale in Euro MEAL_DAY: Verpflegungszuschlag in Euro pro Pflegetag PAID_LEAVE_ALLOWANCES: Bezüge für bezahlten Urlaub. Dies ist ein Prozentsatz, der auf das Grundgehalt angewendet wird.

Die Entität [Indemnite] sieht wie folgt aus:
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 and setters
....
}
Die verschiedenen Felder der Klasse spiegeln die verschiedenen Spalten der Tabelle [INDEMNITIES] wider.
Die Entität [Cotisation] ist die Objektdarstellung einer Zeile in der Tabelle [COTISATIONS]:
ID: Primärschlüssel vom Typ autoincrement VERSION: Versionsnummer des Datensatzes SECU: Sozialversicherungsbeitragssatz (Prozent) socialPENSION: Rentenbeitragssatz CSGD: Beitragssatz für die allgemeine Sozialversicherung Generalized Deductible: Selbstbehalt CSGRDS: Beitragssatz für die Rückzahlung der Sozialschulden

Die Entität [Contribution] sieht wie folgt aus:
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 and setters
...
}
Die verschiedenen Felder der Klasse entsprechen den verschiedenen Spalten der Tabelle [INDEMNITES].
18.4. -basierte Methode zur Berechnung des Gehalts eines Kinderbetreuers
Die Webanwendung, die wir erstellen werden, ermöglicht es uns, das Gehalt eines Mitarbeiters anhand von drei Informationen zu berechnen:
- den Index des Mitarbeiters
- die Anzahl der gearbeiteten Tage
- die Anzahl der gearbeiteten Stunden
Hier ist ein Screenshot einer Gehaltsabrechnung:

Wir stellen nun die Methode zur Berechnung des Monatsgehalts einer Kinderbetreuerin vor. Diese Methode ist nicht für die Anwendung in der Praxis gedacht. Als Beispiel nehmen wir das Gehalt von Frau Marie Jouveinal, die während des Abrechnungszeitraums 150 Stunden an 20 Tagen gearbeitet hat.
Folgende Faktoren werden berücksichtigt: | | |
Das Grundgehalt des ergibt sich aus der folgenden Formel : | ||
Ein bestimmter Betrag an Sozialversicherungsbeiträgen müssen von diesem Grundgehalt abgezogen werden : | | |
Gesamtbeiträge zur Sozialversicherung: | ||
Darüber hinaus hat die Kinderbetreuungskraft Anspruch für jeden Arbeitstag sowie auf eine Verpflegungspauschale . Somit erhält sie die : | ||
Letztendlich ergibt sich folgendes Nettogehalt, das an die Kinderbetreuerin zu zahlen ist: |
18.5. Die Schnittstelle der [business]-Ebene
Werfen wir einen Blick auf die Architektur der Anwendung, die wir entwickeln:
![]() |
Die [Web-/Struts 2]-Schicht kommuniziert mit der Schnittstelle der [Business]-Schicht. Dies geschieht wie folgt:
package metier;
import java.util.List;
import jpa.Employe;
public interface IMetier {
// get your payslip
FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
// list of employees
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-Dropdown-Menü füllen können
Die Methode calculatePaystub gibt eine Instanz der folgenden Klasse [Paystub] zurück:
package metier;
import java.io.Serializable;
import jpa.Cotisation;
import jpa.Employe;
public class FeuilleSalaire implements Serializable {
// private fields
private Employe employe;
private Cotisation cotisation;
private ElementsSalaire elementsSalaire;
// manufacturers
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 and setters
...
}
Die Gehaltsabrechnung enthält die folgenden Informationen:
- Zeile 10: Angaben zum Arbeitnehmer, dessen Gehalt berechnet wird
- Zeile 11: die verschiedenen Beitragssätze
- Zeile 12: Gehaltsbestandteile
Die Klasse [ElementsSalaire] sieht wie folgt aus:
package metier;
import java.io.Serializable;
public class ElementsSalaire implements Serializable{
// private fields
private double salaireBase;
private double cotisationsSociales;
private double indemnitesEntretien;
private double indemnitesRepas;
private double salaireNet;
// manufacturers
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 and setters
...
}
- Zeilen 8–12: Gehaltsbestandteile
18.6. Die Spring-Konfigurationsdatei
Die Integration der [Business-, DAO- und JPA-]Schichten wird durch die folgende Spring-Konfigurationsdatei geregelt:
<?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">
<!-- application layers -->
<!-- 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" />
<!-- 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>
<!-- the DBCP data source -->
<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>
<!-- transaction manager -->
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<!-- translation of exceptions -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
<!-- persistence -->
<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 [Business-, DAO-, JPA-]Schichten erforderlich. Unsere Webanwendung, die auf diesen Schichten basiert, muss daher diese Konfiguration übernehmen. Beachten Sie, dass die Zeilen 32–37 die JDBC-Eigenschaften der Datenbank konfigurieren. Leser, die die Datenbank ändern möchten, müssen diese Zeilen anpassen.
Weitere Informationen zu dieser Konfiguration finden Sie im Dokument „Introduction to Java EE 5“, das unter der URL [http://tahe.developpez.com/java/javaee] verfügbar ist.








