Skip to content

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.

Image

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:

Image

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

Image

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:

Image

Tabelle [indemnites]

  • in [6] die Zulagentabelle und in [7] die Bedeutung ihrer Felder

Der Inhalt der Tabelle könnte wie folgt aussehen:

Image

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

#
# 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;

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)

Image

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.

Image

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

Image

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:

Image

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:

[TOTALHOURS]: Gesamtzahl der im Monat geleisteten Arbeitsstunden

[TOTALDAYS]: Gesamtzahl der im Monat gearbeiteten Tage
[TOTALHOURS]=150
[TOTALDAYS] = 20
Das Grundgehalt des
ergibt sich aus der folgenden Formel
:
[BASESALARY]=([TOTALHOURS]*[HOURLYRATE])*(1+[CPALLOWANCE]/100)
[BASESALARY]=(150*[2.1])*(1+0.15)= 362,25
Ein bestimmter Betrag an Sozialversicherungsbeiträgen
müssen von diesem Grundgehalt abgezogen werden
:

Allgemeiner Sozialbeitrag und Beitrag zur Tilgung der Sozialschuld: [BASESALARY]*[CSGRDS/100]

Abzugsfähiger allgemeiner Sozialbeitrag: [BASESALARY]*[CSGD/100]

Sozialversicherung, Witwenrente, Altersrente: [BASISGEHALT]*[SECU/100]

Zusatzrente + AGPF + Arbeitslosenversicherung: [BASISGEHALT]*[RENTE/100]
CSGRDS: 12,64
CSGD: 22,28
Sozialversicherung: 34,02
Rente: 28,55
Gesamtbeiträge zur Sozialversicherung:
[SOCIALCONTRIBUTIONS] = [BASESALARY] * (CSGRDS + CSGD + SECU + RETIREMENT) / 100
[SOCIALCONTRIBUTIONS]=97,48
Darüber hinaus hat die Kinderbetreuungskraft Anspruch
für jeden Arbeitstag
sowie auf eine Verpflegungspauschale
. Somit erhält sie die
:
[Zulagen] = [TOTALDAYS] * (DAILYMAINTENANCE + DAILYMEAL)
[ZULAGEN]=104
Letztendlich ergibt sich folgendes Nettogehalt, das an die Kinderbetreuerin zu zahlen ist:
[GRUNDGEHALT] – [SOZIALVERSICHERUNGSBEITRÄGE] + [ZULAGEN]
[NETTOGEHALT] = 368,77

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.