5. Version 1: Spring-Architektur / JPA
Es soll eine Konsolenanwendung sowie eine grafische Anwendung erstellt werden, mit denen die Gehaltsabrechnungen der von der „Maison de la petite enfance“ einer Gemeinde beschäftigten Tagesmütter erstellt werden können. Diese Anwendung wird folgende Architektur aufweisen:
![]() |
5.1. BD Die Datenbank
Die statischen Daten, die für die Erstellung der Gehaltsabrechnung benötigt werden, werden in einer Datenbank gespeichert, die wir im Folgenden als „ dbpam“ bezeichnen. Diese Datenbank könnte folgende Tabellen enthalten:
Struktur:
Primärschlüssel | |
Versionsnummer – erhöht sich bei jeder Änderung der Zeile | |
Sozialversicherungsnummer des Mitarbeiters – eindeutig | |
Name des Mitarbeiters | |
sein Vorname | |
seine Adresse | |
seine Stadt | |
seine Postleitzahl | |
Fremdschlüssel im Feld [ID] der Tabelle [INDEMNITES] |
Sein Inhalt könnte wie folgt lauten:

Struktur:
Primärschlüssel | |
Versionsnummer – erhöht sich bei jeder Änderung der Zeile | |
Prozentsatz: allgemeiner Sozialbeitrag + Beitrag zur Tilgung der Sozialschuld | |
Prozentsatz: abzugsfähiger allgemeiner Sozialbeitrag | |
Prozentsatz: Sozialversicherung, Witwenrente, Altersrente | |
Prozentsatz: Zusatzrente + Arbeitslosenversicherung |
Der Inhalt könnte wie folgt lauten:
![]()
Die Sozialversicherungsbeiträge sind unabhängig vom Arbeitnehmer. Die vorstehende Tabelle enthält nur eine Zeile.
Primärschlüssel | ||
Versionsnummer – erhöht sich bei jeder Änderung der Zeile | ||
Verarbeitungsindex – eindeutig | ||
Nettopreis in Euro für eine Stunde Bereitschaftsdienst | ||
Verpflegungszuschlag in Euro pro Betreuungs-Tag | ||
Verpflegungszulage in Euro pro Betreuungs-Tag | ||
Urlaubsgeld. Es handelt sich um einen Prozentsatz, der auf das Grundgehalt anzuwenden ist. | ||
Der Inhalt könnte wie folgt lauten:

Es ist zu beachten, dass die Zulagen von einer Tagesmutter zur anderen variieren können. Sie sind nämlich über den Gehaltsindex der jeweiligen Tagesmutter mit dieser verknüpft. So hat Frau Marie Jouveinal, die einen Gehaltsindex von 2 hat (Tabelle EMPLOYES), einen Stundenlohn von 2,1 Euro (Tabelle INDEMNITES).
5.2. Berechnungsweise des Gehalts einer Tagesmutter
Im Folgenden stellen wir 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 Tagesmutter ergibt sich aus der folgenden Formel: | ||
Ein bestimmter Betrag an Sozialbeiträgen müssen von diesem Grundgehalt abgezogen werden: | | |
Gesamtsumme der Sozialbeiträge: | ||
Darüber hinaus hat die Tagesmutter für jeden gearbeiteten Tag Anspruch auf eine Aufwandsentschädigung sowie eine Verpflegungszulage. In diesem Zusammenhang erhält sie folgende Zulagen: | | |
Insgesamt ergibt sich folgender Nettolohn, der an die Tagesmutter zu zahlen ist: |
5.3. Funktionsweise der Konsolenanwendung
Hier ist ein Beispiel für die Ausführung der Konsolenanwendung in einem DOS-Fenster:
Wir werden ein Programm schreiben, das die folgenden Informationen entgegennimmt:
- Sozialversicherungsnummer der Tagesmutter (im Beispiel 254104940426058 – Zeile 1)
- Gesamtzahl der gearbeiteten Stunden (im Beispiel 150 – Zeile 1)
- Gesamtzahl der gearbeiteten Tage (im Beispiel 20 – Zeile 1)
Es ist zu erkennen, dass:
- Zeilen 9–14: zeigen die Informationen zu dem Mitarbeiter an, dessen Sozialversicherungsnummer angegeben wurde
- Zeilen 17–20: zeigen die Sätze der verschiedenen Beiträge an
- Zeilen 23–26: zeigen die Zulagen an, die mit dem Gehaltsindex des Mitarbeiters verbunden sind (hier Index 2)
- Zeilen 29–33: zeigen die Bestandteile des zu zahlenden Gehalts an
Die Anwendung meldet eventuelle Fehler:
Aufruf ohne Parameter:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar
Syntaxe : pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés
Aufruf mit fehlerhaften Daten:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150x 20x
Le nombre d'heures travaillées [150x] est erroné
Le nombre de jours travaillés [20x] est erroné
Anruf mit falscher Sozialversicherungsnummer:
dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar xx 150 20
L'erreur suivante s'est produite : L'employé de n°[xx] est introuvable
5.4. Funktionsweise der grafischen Anwendung
Die grafische Anwendung ermöglicht die Berechnung der Gehälter von Tagesmüttern über ein Swing-Formular:
![]() |
- Die Informationen, die zuvor als Parameter an das Konsolenprogramm übergeben wurden, werden nun über die Eingabefelder [1, 2, 3] eingegeben.
- Die Schaltfläche [4] startet die Gehaltsberechnung
- Das Formular zeigt die verschiedenen Gehaltsbestandteile bis hin zum auszuzahlenden Nettogehalt an: [5]
Die Dropdown-Liste [1, 6] zeigt nicht die Personalnummern SS der Mitarbeiter an, sondern deren Vor- und Nachnamen. Dabei wird davon ausgegangen, dass es keine zwei Mitarbeiter mit demselben Vor- und Nachnamen gibt.
5.5. Erstellung der Datenbank
Wir starten WampServer und verwenden das Tool PhpMyAdmin [1]:
![]() |
- in [2] wählen wir die Option [Bases de données],
![]() |
- in [3], man erstellt eine Datenbank [dbpam_hibernate],
- bei [4] die erstellte Datenbank. Diese wird ausgewählt,
![]() |
- in [5], man möchte ein Skript SQL importieren,
- in [6], man verwendet die Schaltfläche [Parcourir], um die Datei auszuwählen,
![]() |
- in [7,8] wählen wir das Skript SQL aus,
- in [9] wird es ausgeführt,
![]() |
- in [10] wurden die Tabellen angelegt. Ihr Inhalt lautet wie folgt:
Tabelle EMPLOYES

Tabelle
Tabelle INDEMNITES

Tabelle
Tabelle COTISATIONS
![]()
5.6. Implementierung JPA
5.6.1. Schicht JPA / Hibernate
Wir werden die Schicht JPA in der folgenden Umgebung konfigurieren:
![]() |
Ein Konsolenprogramm wird mit der Datenbank arbeiten. Dazu ist Folgendes erforderlich:
- eine Datenbank vorhanden sein,
- den Treiber JDBC für SGBD, hier MySQL,
- die Schicht JPA mit Hibernate implementieren,
- das Konsolenprogramm zu schreiben.
Wir erstellen das Maven-Projekt [mv-pam-jpa-hibernate] [1]:
![]() |
In der Architektur unserer Anwendung benötigen wir folgende Elemente:
- die Datenbank,
- den Treiber JDBC für SGBD und MySQL,
- die JPA-/Hibernate-Schicht (Entitäten und Konfiguration),
- das Test-Konsolenprogramm.
5.6.1.1. Die Datenbank
Zunächst erstellen wir die leere Datenbank. Wir starten WampServer und verwenden das Tool PhpMyAdmin [1]:
![]() |
- in [2] wählen wir die Option [Bases de données],
![]() |
- in [3] wird eine Datenbank [dbpam_hibernate] erstellt,
- bei [4] ist die Datenbank angelegt.
5.6.1.2. Konfiguration der Schicht JPA
Die Verbindung zwischen der Schicht JDBC und der Datenbank erfolgt über die Datei [persistence.xml], die die Schicht JPA konfiguriert. Diese Datei kann mit NetBeans erstellt werden:
![]() |
- Auf der Registerkarte „[services] [1]“ wird die Verbindung zur Datenbank über den Treiber „JDBC“ von „MySQL [2]“ hergestellt,
- in [3] den Namen der Datenbank, mit der eine Verbindung hergestellt werden soll.
- in [4] den Benutzernamen der Datenbank,
- in [5], man meldet sich als root ohne Passwort an,
- bei [6] kann man die Verbindung testen,
- in [7] ist die Verbindung erfolgreich hergestellt.
![]() |
- Die Verbindung wird unter [8] und unter [9] angezeigt,
- in [10] wird dem Projekt ein neues Element hinzugefügt,
![]() |
- in [11] wählt man die Kategorie [Persistence] und in [12] das Element [Persistence Unit],
- in [13] wird dieser Persistenz-Einheit ein Name gegeben,
- in [14] wählen wir eine Hibernate-Implementierung aus,
- in [15] wird die soeben erstellte Verbindung zur Datenbank MySQL angegeben,
- in [16] geben wir an, dass bei der Instanziierung der Schicht JPA diese die Tabellen erstellen (create) muss, die den Entitäten JPA des Projekts entsprechen.
Am Ende des Assistenten wird die Datei [persistence.xml] generiert:
![]() |
- Die Datei erscheint in einem neuen Zweig des Projekts, in einem Ordner [META-INF] [1],
- der dem Ordner „[src/main/resources]“ des Projekts „[2,3]“ entspricht.
Ihr Inhalt lautet wie folgt:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
- Zeile 3: Der Name der Persistenz-Einheit und der Transaktionstyp. RESOURCE_LOCAL gibt an, dass das Projekt die Transaktionen selbst verwaltet. Dies muss hier das Konsolenprogramm übernehmen,
- Zeile 4: Die verwendete Implementierung JPA ist Hibernate,
- Zeilen 6–9: Die Eigenschaften JDBC der Datenbankverbindung,
- Zeile 11: Fordert die Erstellung der Tabellen an, die den Entitäten JPA entsprechen. Tatsächlich generiert NetBeans hier eine fehlerhafte Konfiguration. Die Konfiguration muss wie folgt lauten:
<property name="hibernate.hbm2ddl.auto" value="create"/>
Mit der Option „create“ löscht Hibernate bei der Instanziierung der Schicht JPA die den Entitäten JPA entsprechenden Tabellen und legt sie anschließend neu an. Die Option „create-drop“ bewirkt dasselbe, löscht jedoch am Ende der Lebensdauer der Schicht JPA alle Tabellen. Es gibt noch eine weitere Option:
<property name="hibernate.hbm2ddl.auto" value="update"/>
Diese Option legt die Tabellen an, falls sie noch nicht vorhanden sind, löscht sie jedoch nicht, wenn sie bereits existieren.
Wir fügen der Hibernate-Konfiguration drei weitere Eigenschaften hinzu:
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
Sie weisen Hibernate an, die Befehle SQL anzuzeigen, die es an die Datenbank sendet. Die vollständige Datei sieht somit wie folgt aus:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
</properties>
</persistence-unit>
</persistence>
5.6.1.3. Die Abhängigkeiten
Kommen wir zurück zur Projektarchitektur:
![]() |
Wir haben die Schicht JPA über die Datei [persistence.xml] konfiguriert. Als Implementierung wurde Hibernate gewählt. Dies hat zu folgenden Abhängigkeiten im Projekt geführt:
![]() |
Diese Abhängigkeiten sind auf die Einbindung von Hibernate in das Projekt zurückzuführen. Wir müssen eine weitere Abhängigkeit hinzufügen, nämlich den Treiber JDBC von MySQL, der die Schicht JDBC der Architektur implementiert. Wir passen die Datei [pom.xml] wie folgt an:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
</dependency>
...
<dependency>
<groupId>org.hibernate.common</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>4.0.1.Final</version>
</dependency>
</dependencies>
In den Zeilen 8–12 wird die Abhängigkeit des Treibers JDBC von MySQL hinzugefügt.
5.6.1.4. Die Entitäten JPA
![]() |
Frage: Erstellen Sie gemäß der Vorgehensweise im Beispiel aus Abschnitt 4.4 die Entitäten [Cotisation, Indemnite, Employe].
Hinweise:
- Die Entitäten sind Teil eines Pakets mit dem Namen [jpa],
- jede Entität erhält eine Versionsnummer,
- wenn zwei Entitäten durch eine Beziehung verknüpft sind, wird nur die Hauptbeziehung @ManyToOne angelegt. Die umgekehrte Beziehung @OneToMany wird nicht angelegt.
5.6.1.5. Der Code der Hauptklasse
Wir fügen dem Projekt die zuvor entwickelten Entitäten JPA und [1] hinzu:
![]() |
Anschließend fügen wir [2] sowie die folgende Klasse [main.Main] hinzu:
package main;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class Main {
public static void main(String[] args) {
// Das Anlegen des Entity-Managers reicht aus, um die Schicht JPA aufzubauen
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpa-hibernatePU");
EntityManager em=emf.createEntityManager();
// Ressourcen freigeben
em.close();
emf.close();
}
}
- Zeile 10: Wir erstellen die EntityManagerFactory der Persistenz-Einheit mit dem Namen [mv-pam-jpa-hibernatePU]. Dieser Name stammt aus der Datei [persistence.xml]:
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
...
</persistence-unit>
- Zeile 12: Es wird die Datei „EntityManager“ erstellt. Durch diese Erstellung wird die Ebene „JPA“ angelegt. Die Datei „[persistence.xml]“ wird verarbeitet, wodurch die Datenbanktabellen angelegt werden.
- Zeilen 14–15: Die Ressourcen werden freigegeben.
5.6.1.6. Tests
Kommen wir zurück zur Architektur unseres Projekts:
![]() |
Alle Schichten wurden implementiert. Wir führen das Projekt [2] aus.
![]() |
Die Konsolenausgabe lautet wie folgt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | |
In der Konsole sind ausschließlich Hibernate-Protokolleinträge zu finden, da das ausgeführte Programm nichts anderes tut, als die Schicht JPA zu instanziieren. Folgende Punkte sind zu beachten:
- Zeile 43: Hibernate versucht, den Fremdschlüssel aus der Tabelle [EMPLOYES] zu löschen,
- Zeilen 51–55: Löschen der drei Tabellen,
- Zeile 57: Erstellung der Tabelle [COTISATIONS],
- Zeile 67: Erstellung der Tabelle [EMPLOYES],
- Zeile 80: Erstellung der Tabelle [INDEMNITES],
- Zeile 91: Anlegen des Fremdschlüssels der Tabelle [EMPLOYES].
In NetBeans sind die Tabellen in der zuvor erstellten Verbindung zu sehen:
![]() |
Die angelegten Tabellen hängen sowohl von der Implementierung der verwendeten Schicht JPA als auch von der verwendeten Schicht SGBD ab. So kann eine Implementierung von JPA / EclipseLink mit derselben Datenbank unterschiedliche Tabellen erzeugen. Das werden wir nun untersuchen.
5.6.2. Schicht JPA / EclipseLink
Wir werden ein neues Maven-Projekt in der folgenden Umgebung erstellen:
![]() |
Wir werden die Vorgehensweise aus dem vorherigen Abschnitt befolgen:
- Erstellen einer Datenbank mit den Namen MySQL und [dbpam_eclipselink]. Zur Erstellung verwenden wir das Skript [dbpam_eclipselink.sql],
- die Projektdatei [persistence.xml] anlegen. Die Implementierung JPA 2.0 EclipseLink übernehmen,
- fügen Sie in die generierten Abhängigkeiten die Abhängigkeit des Treibers JDBC von MySQL hinzu,
- die Entitäten JPA und das Konsolenprogramm hinzufügen,
- Tests durchführen.
Die Datei [persistence.xml] sieht dann wie folgt aus:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="pam-jpa-eclipselinkPU" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="eclipselink.target-database" value="MySQL"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
- Die Eigenschaften 9–13 wurden vom NetBeans-Assistenten generiert,
- Zeile 14: Mit dieser Eigenschaft können wir die Protokollierungsstufe von EclipseLink festlegen. Anhand der Stufe von FINE können wir erkennen, welche Befehle SQL an die Datenbank senden wird,
- Zeile 15: Bei der Instanziierung der Schicht JPA / EclipseLink werden die Tabellen der Entitäten JPA zunächst gelöscht und anschließend neu angelegt.
Die folgenden Konsolenausgaben werden angezeigt:
- Zeilen 26–30: Verbindung zur Datenbank MySQL,
- Zeilen 31–34: Bestätigung, dass die Verbindung erfolgreich hergestellt wurde,
- Zeile 36: Löschen des Fremdschlüssels aus der Tabelle [EMPLOYES],
- Zeile 37: Löschen der Tabelle [COTISATIONS],
- Zeile 38: Erstellung der Tabelle [COTISATIONS]. Interessanterweise weist der Primärschlüssel ID nicht das Attribut MySQL auto_increment auf. Das bedeutet, dass nicht MySQL die Werte des Primärschlüssels generiert,
- Zeile 39: Löschen der Tabelle [EMPLOYES],
- Zeile 40: Erstellung der Tabelle [EMPLOYES]. Ihr Primärschlüssel ID enthält nicht die Attribute MySQL und auto_increment,
- Zeile 41: Löschen der Tabelle [INDEMNITES],
- Zeile 42: Erstellung der Tabelle [INDEMNITES]. Ihr Primärschlüssel ID hat nicht die Attribute MySQL und auto_increment,
- Zeile 43: Erstellung des Fremdschlüssels von der Tabelle [EMPLOYES] zur Tabelle [INDEMNITES],
- Zeile 44: Erstellung einer Tabelle [SEQUENCE]. Diese wird zur Generierung der Primärschlüssel der drei vorherigen Tabellen verwendet,
- Zeile 47: Es tritt eine Ausnahme auf, da diese Tabelle bereits existierte,
- Zeilen 51–53: Initialisierung der Tabelle [SEQUENCE].
Die Existenz der generierten Tabellen kann in NetBeans überprüft werden: [1]:
![]() |
Ausgehend von denselben Entitäten JPA generieren die Implementierungen JPA, Hibernate und EclipseLink also nicht dieselben Tabellen. Im weiteren Verlauf des Dokuments wird, wenn die verwendete Implementierung JPA lautet:
- Hibernate, wird die Datenbank [dbpam_hibernate] verwendet,
- EclipseLink verwendet wird, wird die Datenbank [dbpam_eclipselink] verwendet.
5.6.3. Zu erledigende Aufgabe
Nach dem gleichen Vorgehen wie zuvor
- ein Projekt [mv-pam-jpa-hibernate-oracle] erstellen und testen, das eine Hibernate-Implementierung JPA und eine Oracle-Implementierung SGBD verwendet,
- ein Projekt [mv-pam-jpa-hibernate-mssql] erstellen und testen, das eine Hibernate-Implementierung JPA und einen SGBD-Server verwendet,
- Erstellen und Testen eines [mv-pam-jpa-eclipselink-oracle]-Projekts unter Verwendung einer JPA-Implementierung, EclipseLink und eines SGBD-Oracle-Servers,
- Erstellen und Testen eines [mv-pam-jpa-eclipselink-mssql]-Projekts unter Verwendung einer JPA-Implementierung, EclipseLink und eines SGBD-SQL-Servers,
5.6.4. Lazy oder Eager?
Kommen wir zurück zu einer möglichen Definition der Entität [Employe]:
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(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
...
}
In den Zeilen 27–29 wird der Fremdschlüssel von der Tabelle [EMPLOYES] zur Tabelle [INDEMNITES] definiert. Das Attribut „fetch“ in Zeile 27 definiert die Suchstrategie für das Feld indemnite in Zeile 29. Es gibt zwei Modi:
- FetchType.LAZY: Bei der Suche nach einem Mitarbeiter wird die ihm entsprechende Zulage nicht zurückgegeben. Sie wird erst zurückgegeben, wenn das Feld [Employe].indemnite zum ersten Mal referenziert wird.
- FetchType.EAGER: Bei der Suche nach einem Mitarbeiter wird die ihm entsprechende Zulage angezeigt. Dies ist der Standardmodus, wenn kein Modus angegeben wird.
Um den Nutzen der Option FetchType.LAZY zu verstehen, kann man das folgende Beispiel heranziehen. Eine Liste von Mitarbeitern ohne Zulagen wird auf einer Webseite mit einem Link [Details] angezeigt. Ein Klick auf diesen Link zeigt dann die Zulagen des ausgewählten Mitarbeiters an. Dabei zeigt sich:
- Für die Anzeige der ersten Seite werden die Mitarbeiter mit ihren Zulagen nicht benötigt. Der Modus FetchType.LAZY ist daher geeignet,
- um die zweite Seite mit den Details anzuzeigen, muss eine zusätzliche Abfrage an die Datenbank gestellt werden, um die Zulagen des ausgewählten Mitarbeiters abzurufen.
Der Modus FetchType.LAZY verhindert, dass zu viele Daten abgerufen werden, die die Anwendung nicht sofort benötigt. Sehen wir uns ein Beispiel an.
Das Projekt [mv-pam-jpa-hibernate] wird dupliziert:
![]() |
- in [1]; dabei wird das Projekt kopiert,
- in [2] wird der Ordner für die Kopie angegeben und in [3] der Name,
- in [4] trägt das neue Projekt denselben Namen wie das alte. Das ändern wir:
![]() |
- in [1], das Projekt wird umbenannt,
- in [2], benennen das Projekt um und sein artifactId,
- in [3], das neue Projekt.
Wir ändern das Programm [Main.java] wie folgt:
package main;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import jpa.Employe;
public class Main {
// Die folgende Abfrage JPQL liefert einen Mitarbeiter
// Der Fremdschlüssel [Employe].indemnite befindet sich in FetchType.LAZY
public static void main(String[] args) {
// Es reicht aus, den Entity Manager zu erstellen, um die Ebene JPA aufzubauen
EntityManagerFactory emf = Persistence.createEntityManagerFactory("pam-jpa-hibernatePU");
// erster Versuch
EntityManager em = emf.createEntityManager();
Employe employe = (Employe) em.createQuery("select e from Employe e where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
em.close();
// Der Mitarbeiter wird angezeigt
try {
System.out.println(employe);
} catch (Exception ex) {
System.out.println(ex);
}
// zweiter Versuch
em = emf.createEntityManager();
employe = (Employe) em.createQuery("select e from Employe e left join fetch e.indemnite where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
// Ressourcen freigeben
em.close();
// Mitarbeiter anzeigen
try {
System.out.println(employe);
} catch (Exception ex) {
System.out.println(ex);
}
// Ressourcen freigeben
emf.close();
}
}
- Zeile 15: Wir erstellen das EntityManagerFactory aus der Ebene JPA,
- Zeile 17: Wir erhalten das Programm EntityManager, das uns die Kommunikation mit der Ebene JPA ermöglicht,
- Zeile 18: Der Mitarbeiter mit dem Namen Jouveinal wird abgefragt,
- Zeile 19: EntityManager wird geschlossen. Dadurch wird der Persistenzkontext geschlossen.
- Zeile 22: Der empfangene Mitarbeiter wird angezeigt.
Die Klasse [Employe] 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(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
/**
* Returns a string representation of the object. This implementation constructs
* that representation based on the id fields.
* @return a string representation of the object.
*/
@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()
+"]";
}
...
}
- Zeile 27: Das Feld indemnite wird in den Modus LAZY zurückgesetzt,
- Zeile 47: Verwendet das Feld indemnite. Wird die Methode toString aufgerufen, obwohl das Feld indemnite noch nicht zurückgesetzt wurde, erfolgt die Rücksetzung zu diesem Zeitpunkt. Es sei denn, der Persistenzkontext wurde wie im Beispiel geschlossen.
Kehren wir zum Code von [Main] zurück:
- Zeilen 21–25: Hier sollte eine Ausnahme auftreten. Tatsächlich wird die Methode toString aufgerufen. Diese verwendet das Feld indemnite. Nach diesem Feld wird gesucht. Da der Persistenzkontext geschlossen wurde, existiert die zurückgegebene Entität [Employe] nicht mehr, woraus sich die Ausnahme ergibt.
- Zeile 27: Es wird ein neues EntityManager angelegt,
- Zeile 28: Der Mitarbeiter Jouveinal wird abgefragt, wobei in der Abfrage JPQL ausdrücklich die dazugehörige Zulage angefordert wird. Diese ausdrückliche Anforderung ist notwendig, da der Suchmodus für diese Zulage LAZY ist,
- Zeile 30: EntityManager wird geschlossen,
- Zeilen 32–36: Der Mitarbeiter wird erneut angezeigt. Es sollte keine Ausnahme auftreten.
Um das Projekt auszuführen, benötigt man eine gefüllte Datenbank. Diese wird gemäß der Vorgehensweise in Abschnitt 5.5 angelegt. Außerdem muss die Datei [persistence.xml] geändert werden:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="mv-pam-jpa-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>jpa.Cotisation</class>
<class>jpa.Employe</class>
<class>jpa.Indemnite</class>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbpam_hibernate"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
</properties>
</persistence-unit>
</persistence>
- Die Option zum Anlegen der Tabellen wurde entfernt. Die Datenbank ist hier bereits vorhanden und gefüllt,
- und wir haben die Optionen entfernt, die dazu führten, dass Hibernate die Befehle SQL protokollierte, die es an die Datenbank sendete.
Die Ausführung des Projekts führt zu den folgenden beiden Ausgaben in der Konsole:
- Zeile 1: Die Ausnahme, die auftrat, als die fehlende Zulage gesucht werden sollte, während die Sitzung geschlossen war. Man sieht, dass die Zulage aufgrund des Modus LAZY nicht zurückgegeben wurde,
- Zeile 2: Der Mitarbeiter mit seiner Zulage, die durch eine Abfrage ermittelt wurde, die den Modus LAZY umgangen hat.
5.6.5. Zu erledigende Aufgabe
Erstellen Sie nach einem ähnlichen Vorgehen wie oben beschrieben ein Projekt mit dem Namen [mv-pam-pa-eclipselink-lazy], das das Verhalten von EclipseLink im Vergleich zum Modus LAZY veranschaulicht.
Es ergeben sich folgende Ergebnisse:
Im Modus LAZY haben beide Abfragen die Zulage dem Mitarbeiter zugeordnet. Wenn man sich im Internet über diese Unstimmigkeit informiert, stellt man fest, dass der Eintrag [FetchType.LAZY] (Zeile 1):
@ManyToOne(fetch= FetchType.LAZY)
@JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;
keine Anweisung, sondern lediglich eine Empfehlung ist. Der Implementierer JPA ist nicht verpflichtet, dieser zu folgen. Man sieht also, dass der Code manchmal von der verwendeten Implementierung JPA abhängig wird. Es ist möglich, EclipseLink per Konfiguration das für den Modus LAZY erwartete Verhalten zuzuweisen.
5.6.6. Für den weiteren Verlauf
Die Architektur der zu erstellenden Anwendung sieht wie folgt aus:
![]() |
Im weiteren Verlauf des Dokuments wird das Maven-Projekt [mv-pam-jpa-hibernate] in die Projekte [mv-pam-spring-hibernate] und [1, 2, 3] dupliziert:
![]() |
- Anschließend wird das neue Projekt in „[4, 5, 6]“ umbenannt.
Die Abhängigkeiten des neuen Projekts werden geändert. Die Datei [pom.xml] sieht nun wie folgt aus:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-pam-spring-hibernate</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-spring-hibernate</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>swing-layout</id>
<layout>default</layout>
<name>Repository for library Library[swing-layout]</name>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
</project>
- Zeilen 25–31: die Abhängigkeit für die Tests JUnit,
- Zeilen 32–41: die Abhängigkeiten für den Apache-Verbindungspool DBCP,
- Zeilen 42–65: die Abhängigkeiten für das Spring-Framework,
- Zeilen 67–71: die Abhängigkeiten für die Implementierung JPA / Hibernate,
- Zeilen 72–76: die Abhängigkeit für den Treiber JDBC von MySQL,
- Zeilen 77–81: die Abhängigkeit für die Swing-Schnittstelle. Diese wird von NetBeans automatisch hinzugefügt, wenn man dem Projekt eine Swing-Schnittstelle hinzufügt.
Außerdem werden die beiden Datenbanken MySQL generiert:
- [dbpam_hibernate] aus dem Skript [dbpam_hibernate.sql],
- [dbpam_eclipselink] aus dem Skript [dbpam_eclipselink.sql],
5.7. Die Schnittstellen- n der Ebenen [metier] und [DAO]
Kommen wir zurück zur Architektur der Anwendung:
![]() |
Welche Schnittstelle muss in der oben dargestellten Architektur die Schicht [DAO] der Schicht [metier] bereitstellen und welche Schnittstelle muss die Schicht [metier] der Schicht [ui] bereitstellen? Ein erster Ansatz zur Definition der Schnittstellen der verschiedenen Schichten besteht darin, die verschiedenen Anwendungsfälle (Use Cases) der Anwendung zu untersuchen. Hier haben wir zwei, je nach gewählter Benutzeroberfläche: Konsole oder grafisches Formular.
Betrachten wir die Nutzung der Konsolenanwendung:
Die Anwendung erhält drei Informationen vom Benutzer (siehe Zeile 1 oben)
- die Sozialversicherungsnummer der Tagesmutter
- die Anzahl der im Monat geleisteten Arbeitsstunden
- die Anzahl der im Monat gearbeiteten Tage
Anhand dieser Angaben und weiterer Informationen, die in Konfigurationsdateien gespeichert sind, zeigt die Anwendung folgende Informationen an:
- Zeilen 4–6: die eingegebenen Werte
- Zeilen 8–10: die Informationen zu dem Arbeitnehmer, dessen Sozialversicherungsnummer angegeben wurde
- Zeilen 12–14: die Sätze der verschiedenen Sozialbeiträge
- Zeilen 16–17: die verschiedenen an die Tagesmutter gezahlten Zulagen
- Zeilen 19–24: die Bestandteile der Lohnabrechnung der Tagesmutter
Die Schicht [metier] muss der Schicht [ui] eine Reihe von Informationen bereitstellen:
- Informationen zu einer Tagesmutter, die anhand ihrer Sozialversicherungsnummer identifiziert wird. Diese Informationen befinden sich in der Tabelle [EMPLOYES]. Dadurch können die Zeilen 6–8 angezeigt werden.
- die Beträge der verschiedenen Sozialversicherungsbeiträge, die vom Bruttolohn abzuziehen sind. Diese Informationen befinden sich in der Tabelle [COTISATIONS]. Dadurch können die Zeilen 10–12 angezeigt werden.
- die Beträge der verschiedenen Zulagen im Zusammenhang mit der Tätigkeit als Tagesmutter. Diese Informationen finden sich in der Tabelle [INDEMNITES]. Dadurch werden die Zeilen 14–15 angezeigt.
- die Gehaltsbestandteile, die in den Zeilen 18–22 angezeigt werden.
Auf dieser Grundlage könnte man sich für eine erste Buchung der Schnittstelle [IMetier] entscheiden, die von der Ebene [metier] an die Ebene [ui] übermittelt wird:
- Zeile 1: Die Elemente der Schicht [metier] werden in das Paket [metier] übernommen
- Zeile 5: Die Methode [ calculerFeuilleSalaire ] nimmt die drei von der Schicht [ui] erfassten Informationen als Parameter entgegen und gibt ein Objekt vom Typ [FeuilleSalaire] zurück, das die Informationen enthält, die die Schicht [ui] auf der Konsole anzeigen wird. Die Klasse [FeuilleSalaire] könnte wie folgt aussehen:
- Zeile 9: der von der Gehaltsabrechnung betroffene Mitarbeiter – Information Nr. 1, die von der Ebene [ui] angezeigt wird
- Zeile 10: die verschiedenen Beitragssätze – Information Nr. 2, angezeigt durch die Ebene [ui]
- Zeile 11: die verschiedenen Zulagen in Abhängigkeit vom Index des Mitarbeiters – Information Nr. 3, angezeigt durch die Ebene [ui]
- Zeile 12: Die Bestandteile seines Gehalts – Information Nr. 4, angezeigt durch die Ebene [ui]
Ein zweiter Anwendungsfall der Ebene [métier] ergibt sich im Zusammenhang mit der grafischen Benutzeroberfläche:
![]() |
Wie oben zu sehen ist, zeigt die Dropdown-Liste [1, 2] alle Mitarbeiter an. Diese Liste muss von der Ebene [métier] angefordert werden. Die Schnittstelle ace dieser Ebene entwickelt sich dann wie folgt:
- Zeile [10]: Die Methode, mit der die Ebene [ui] die Liste aller Mitarbeiter von der Ebene [métier] anfordern kann.
Die Schicht [metier] kann die Felder [Employe, Cotisation, Indemnite] des oben genannten Objekts [FeuilleSalaire] nur durch Abfrage der Schicht [DAO] initialisieren, da sich diese Informationen in den Datenbanktabellen befinden. Das Gleiche gilt für den Abruf der Liste aller Mitarbeiter. Man könnte eine einzige Schnittstelle [DAO] erstellen, die den Zugriff auf die drei Entitäten [Employe, Cotisation, Indemnite] verwaltet. Wir entscheiden uns hier jedoch dafür, pro Entität eine eigene Schnittstelle [DAO] zu erstellen.
Die Schnittstelle [DAO] für den Zugriff auf die Entitäten [Cotisation] der Tabelle [COTISATIONS] sieht wie folgt aus:
- In Zeile 6 verwaltet die Schnittstelle [ICotisationDao] den Zugriff auf die Entität [Cotisation] und damit auf die Tabelle [COTISATIONS] der Datenbank. Unsere Anwendung benötigt lediglich die Methode [findAll] aus Zeile 16, mit der der gesamte Inhalt der Tabelle [COTISATIONS] abgerufen werden kann. Wir wollten hier einen allgemeineren Fall betrachten, in dem alle Operationen CRUD (Create, Read, Update, Delete) an der Entität durchgeführt werden.
- Zeile 8: Die Methode [create] legt eine neue Entität [Cotisation] an
- Zeile 10: Die Methode [edit] ändert eine vorhandene Entität [Cotisation]
- Zeile 12: Die Methode [destroy] löscht eine vorhandene Entität [Cotisation]
- Zeile 14: Mit der Methode [find] kann eine vorhandene Entität [Cotisation] anhand ihrer ID abgerufen werden
- Zeile 16: Die Methode [findAll] gibt alle vorhandenen Entitäten [Cotisation] in einer Liste zurück
Betrachten wir nun die Signatur der Methode [create]:
Die Methode create hat einen Parameter cotisation vom Typ Cotisation. Der Parameter cotisation muss persistiert werden, c.a.d. Dieser wird hier in die Tabelle [COTISATIONS] geschrieben. Vor dieser Speicherung hat der Parameter cotisation eine Kennung id ohne Wert. Nach der Persistenz weist das Feld id einen Wert auf, der dem Primärschlüssel des in die Tabelle [COTISATIONS] hinzugefügten Datensatzes entspricht. Der Parameter cotisation ist somit ein Eingabe-/Ausgabeparameter der Methode create. Es scheint nicht notwendig zu sein, dass die Methode create zusätzlich den Parameter cotisation als Ergebnis zurückgibt. Da die aufrufende Methode eine Referenz auf das Objekt [Cotisation cotisation] besitzt, hat sie bei einer Änderung dieses Objekts Zugriff auf das geänderte Objekt, da sie eine Referenz darauf hat. Sie kann daher den Wert ermitteln, den die Methode create dem Feld id des Objekts [Cotisation cotisation] zugewiesen hat. Die Signatur der Methode könnte daher einfacher lauten:
Beim Schreiben einer Schnittstelle sollte man bedenken, dass sie in zwei verschiedenen Kontexten verwendet werden kann: local und distant. Im Kontext local werden die aufrufende und die aufgerufene Methode im selben JVM ausgeführt:
![]() |
Wenn die Schicht [metier] die Methode create der Schicht [DAO] aufruft, verfügt sie tatsächlich über eine Referenz auf den Parameter [Cotisation cotisation], den sie an die Methode übergibt.
Im Kontext distant werden die aufrufende und die aufgerufene Methode in unterschiedlichen JVM ausgeführt:
![]() |
Im obigen Beispiel wird die Schicht [metier] in der JVM 1 und die Schicht [DAO] in der JVM 2 auf zwei verschiedenen Rechnern ausgeführt. Die beiden Schichten kommunizieren nicht direkt miteinander. Zwischen ihnen befindet sich eine Schicht, die wir als Kommunikationsschicht [1] bezeichnen. Diese besteht aus einer Sendeschicht [2] und einer Empfangsschicht [3]. Der Entwickler muss diese Kommunikationsschichten in der Regel nicht selbst schreiben. Sie werden automatisch von Software-Tools generiert. Die Schicht [metier] wird so geschrieben, als würde sie in derselben JVM ausgeführt wie die Schicht [DAO]. Es sind daher keine Codeänderungen erforderlich.
Der Kommunikationsmechanismus zwischen der Schicht [metier] und der Schicht [DAO] ist wie folgt:
- Die Schicht [metier] ruft die Methode create der Schicht [DAO] auf und übergibt ihr dabei den Parameter [Cotisation cotisation1]
- Dieser Parameter wird tatsächlich an die Sendeschicht [2] übergeben. Diese überträgt den Wert des Parameters cotisation1 und nicht dessen Referenz über das Netzwerk. Die genaue Form dieses Werts hängt vom verwendeten Kommunikationsprotokoll ab.
- Die Empfangsschicht [3] ruft diesen Wert ab und rekonstruiert daraus ein Objekt [Cotisation cotisation2], das dem ursprünglichen, von der Schicht [metier] gesendeten Parameter entspricht. Nun liegen zwei (inhaltlich) identische Objekte in zwei verschiedenen JVM vor: cotisation1 und cotisation2.
- Die Empfangsschicht übergibt das Objekt cotisation2 an die Methode create der Schicht [DAO], die es in der Datenbank speichert. Nach diesem Vorgang wurde das Feld id des Objekts cotisation2 mit dem Primärschlüssel des Datensatzes initialisiert, der zur Tabelle [COTISATIONS] hinzugefügt wurde. Dies ist nicht der Fall beim Objekt cotisation1, auf das die Ebene [metier] verweist. Wenn die Schicht [metier] eine Referenz auf das Objekt cotisation2 haben soll, muss man ihr diese übermitteln. Daher muss die Signatur der Methode create der Schicht [DAO] geändert werden:
- Mit dieser neuen Signatur gibt die Methode create das persistierte Objekt cotisation2 als Ergebnis zurück. Dieses Ergebnis wird an die Empfangsschicht [3] zurückgegeben, die die Schicht [DAO] aufgerufen hatte. Diese gibt den Wert (und nicht die Referenz) von cotisation2 an die Sendeschicht [2] zurück.
- Die Sendeschicht [2] ruft diesen Wert ab und rekonstruiert daraus ein Objekt [Cotisation cotisation3], das das von der Methode create der Schicht [DAO] zurückgegebene Ergebnis abbildet.
- Das Objekt [Cotisation cotisation3] wird an die Methode der Schicht [metier] zurückgegeben, deren Aufruf der Methode create der Schicht [DAO] diesen gesamten Mechanismus ausgelöst hatte. Die Schicht [metier] kann somit den Wert des Primärschlüssels ermitteln, der dem Objekt [Cotisation cotisation1] zugewiesen wurde, dessen Persistenz sie angefordert hatte: Es handelt sich um den Wert des Feldes id von cotisation3.
Die oben beschriebene Architektur ist nicht die gängigste. Häufiger findet man die Schichten [metier] und [DAO] innerhalb derselben JVM:
![]() |
In dieser Architektur müssen die Methoden der Schicht [metier] Ergebnisse zurückgeben und nicht die der Schicht [DAO]. Dennoch lautet die folgende Signatur der Methode create der Schicht [DAO]:
ermöglicht es uns, keine Annahmen über die tatsächlich implementierte Architektur zu treffen. Die Verwendung von Signaturen, die unabhängig von der gewählten Architektur – ob lokal oder remote – funktionieren, bedeutet, dass für den Fall, dass eine aufgerufene Methode einige ihrer Parameter ändert:
- dass diese ebenfalls Teil des Ergebnisses der aufgerufenen Methode sein müssen
- die aufrufende Methode das Ergebnis der aufgerufenen Methode verwenden muss und nicht die Referenzen der geänderten Parameter, die sie an die aufgerufene Methode übergeben hat.
So bleibt uns die Möglichkeit, ohne Codeänderungen von einer locale-Architektur zu einer distante-Architektur zu wechseln. Betrachten wir vor diesem Hintergrund noch einmal die Schnittstelle [ICotisationDao]:
- Zeile 8: Der Fall der Methode create wurde behandelt
- Zeile 10: Die Methode edit verwendet ihren Parameter [Cotisation cotisation1], um den Datensatz in der Tabelle [COTISATIONS] zu aktualisieren, der denselben Primärschlüssel wie das Objekt cotisation hat. Als Ergebnis liefert sie das Objekt cotisation2, das eine Kopie des geänderten Datensatzes darstellt. Der Parameter cotisation1 wird hingegen nicht geändert. Die Methode muss cotisation2 als Ergebnis zurückgeben, unabhängig davon, ob es sich um eine Architektur vom Typ distante oder locale handelt.
- Zeile 12: Die Methode destroy löscht den Datensatz aus der Tabelle [COTISATIONS], der denselben Primärschlüssel wie das als Parameter übergebene Objekt cotisation hat. Dieses wird nicht geändert. Es muss daher nicht zurückgegeben werden.
- Zeile 14: Der Parameter id der Methode find wird von der Methode nicht verändert. Er muss nicht Teil des Ergebnisses sein.
- Zeile 16: Die Methode findAll hat keine Parameter. Sie muss daher nicht untersucht werden.
Letztendlich muss nur die Signatur der Methode create angepasst werden, damit sie im Rahmen einer Architektur distante verwendet werden kann. Die vorstehenden Überlegungen gelten auch für die anderen Schnittstellen [DAO]. Wir werden sie nicht wiederholen und direkt Signaturen verwenden, die sowohl im Rahmen einer Architektur distante als auch locale einsetzbar sind.
Die Schnittstelle [DAO] für den Zugriff auf die Entitäten [Indemnite] der Tabelle [INDEMNITES] lautet wie folgt:
- In Zeile 6 verwaltet die Schnittstelle [IIndemniteDao] den Zugriff auf die Entität [Indemnite] und damit auf die Tabelle [INDEMNITES] in der Datenbank. Unsere Anwendung benötigt lediglich die Methode [findAll] aus Zeile 16, mit der der gesamte Inhalt der Tabelle [INDEMNITES] abgerufen werden kann. Wir wollten hier einen allgemeineren Fall betrachten, in dem alle Operationen CRUD (Create, Read, Update, Delete) an der Entität durchgeführt werden.
- Zeile 8: Die Methode [create] legt eine neue Entität [Indemnite] an
- Zeile 10: Die Methode [edit] ändert eine vorhandene Entität [Indemnite]
- Zeile 12: Die Methode [destroy] löscht eine vorhandene Entität [Indemnite]
- Zeile 14: Mit der Methode [find] kann eine vorhandene Entität [Indemnite] über ihre Kennung id abgerufen werden
- Zeile 16: Die Methode [findAll] gibt alle vorhandenen Entitäten [Indemnite] in einer Liste zurück
Die Schnittstelle [DAO] für den Zugriff auf die Entitäten [Employe] aus der Tabelle [EMPLOYES] sieht wie folgt aus:
- In Zeile 6 verwaltet die Schnittstelle [IEmployeDao] den Zugriff auf die Entität [Employe] und damit auf die Tabelle [EMPLOYES] der Datenbank. Unsere Anwendung benötigt lediglich die Methode [findAll] aus Zeile 16, mit der der gesamte Inhalt der Tabelle [EMPLOYES] abgerufen werden kann. Wir wollten hier einen allgemeineren Fall betrachten, in dem alle Operationen CRUD (Create, Read, Update, Delete) an der Entität durchgeführt werden.
- Zeile 8: Die Methode [create] legt eine neue Entität [Employe] an
- Zeile 10: Die Methode [edit] ändert eine vorhandene Entität [Employe]
- Zeile 12: Die Methode [destroy] löscht eine vorhandene Entität [Employe]
- Zeile 14: Mit der Methode [find] kann eine vorhandene Entität [Employe] über ihre Kennung id abgerufen werden
- Zeile 16: Mit der Methode [find(String SS)] kann eine vorhandene Entität [Employe] anhand ihrer Nummer SS abgerufen werden. Wir haben gesehen, dass diese Methode für die Konsolenanwendung erforderlich ist.
- Zeile 18: Die Methode [findAll] gibt alle vorhandenen Entitäten [Employe] in einer Liste zurück. Wir haben gesehen, dass diese Methode für die grafische Anwendung erforderlich ist.
5.8. Die Klasse [PamException]
Die Schicht [DAO] arbeitet mit den Java-Klassen API und JDBC zusammen. Diese API löst kontrollierte Ausnahmen vom Typ [SQLException] aus, die zwei Nachteile haben:
- Sie machen den Code schwerfälliger, da dieser diese Ausnahmen zwingend mit try/catch-Anweisungen behandeln muss.
- Sie müssen in der Signatur der Methoden der Schnittstelle [IDao] mit einem „throws SQLException“ deklariert werden. Dies hat zur Folge, dass die Implementierung dieser Schnittstelle durch Klassen verhindert wird, die eine kontrollierte Ausnahme eines anderen Typs als [SQLException] auslösen würden.
Um dieses Problem zu beheben, „leitet“ die Schicht [DAO] nur unkontrollierte Ausnahmen vom Typ [PamException] weiter.
![]() |
- Die Schicht [JDBC] löst Ausnahmen vom Typ [SQLException] aus
- Die Schicht [JPA] löst Ausnahmen aus, die für die verwendete Implementierung JPA spezifisch sind
- Die Schicht [DAO] löst unkontrollierte Ausnahmen vom Typ [PamException] aus
Dies hat zwei Konsequenzen:
- Die Schicht [metier] ist nicht verpflichtet, die Ausnahmen der Schicht [DAO] mit try/catch-Anweisungen zu behandeln. Sie kann diese einfach bis zur Schicht [ui] weiterleiten.
- Die Methoden der Schnittstelle [IDao] müssen in ihrer Signatur nicht die Art der Ausnahme [PamException] angeben, was die Möglichkeit offenlässt, diese Schnittstelle mit Klassen zu implementieren, die einen anderen Typ einer unkontrollierten Ausnahme auslösen würden.
Die Klasse [PamException] wird im Paket [exception] des NetBeans-Projekts abgelegt:
![]() |
Der Code lautet wie folgt:
- Zeile 4: [PamException] leitet sich von [RuntimeException] ab. Es handelt sich also um eine Art von Ausnahmen, für die der Compiler nicht verlangt, dass sie mit einem try/catch-Block abgefangen oder in die Signatur der Methoden aufgenommen werden. Aus diesem Grund ist [PamException] nicht in der Signatur der Methoden der Schnittstelle [IDao] enthalten. Dadurch kann diese Schnittstelle von einer Klasse implementiert werden, die einen anderen Ausnahmetyp auslöst, vorausgesetzt, diese Klasse leitet sich ebenfalls von [RuntimeException] ab.
- Um die möglicherweise auftretenden Fehler zu unterscheiden, wird der Fehlercode aus Zeile 7 verwendet. Die drei Konstruktoren in den Zeilen 14, 19 und 24 sind diejenigen der übergeordneten Klasse [RuntimeException], denen ein Parameter hinzugefügt wurde: der Fehlercode, der der Ausnahme zugewiesen werden soll.
Die Funktionsweise der Anwendung im Hinblick auf Ausnahmen ist wie folgt:
- Die Schicht [DAO] kapselt jede auftretende Ausnahme in eine Ausnahme vom Typ [PamException] und leitet diese an die Schicht [métier] weiter.
- Die Schicht [métier] lässt die von der Schicht [DAO] ausgelösten Ausnahmen weiterleiten. Sie kapselt jede in der Schicht [métier] auftretende Ausnahme in eine Ausnahme vom Typ [PamException] und leitet diese an die Schicht [ui] weiter.
- Die Schicht [ui] fängt alle Ausnahmen ab, die aus den Schichten [métier] und [DAO] weitergeleitet werden. Sie beschränkt sich darauf, die Ausnahme auf der Konsole oder der grafischen Benutzeroberfläche anzuzeigen.
Betrachten wir nun nacheinander die Implementierung der Schichten [DAO] und [metier].
5.9. Die Schicht [DAO] der Anwendung [PAM]
Wir betrachten den folgenden Architekturkontext:
![]() |
5.9.1. Implementierung
Empfohlene Lektüre: Abschnitt 3.1.3 von [ref1]
Aufgabe: Schreiben Sie unter Verwendung der Spring-Integration / JPA die Klassen [CotisationDao, IndemniteDao, EmployeDao] zur Implementierung der Schnittstellen [ICotisationDao, IIndemniteDao, IEmployeDao]. Jede Klassenmethode fängt eine mögliche Ausnahme ab und kapselt sie in eine Ausnahme vom Typ [PamException] mit einem für die abgefangene Ausnahme spezifischen Fehlercode.
Die Implementierungsklassen werden Teil des Pakets [dao] sein:
![]() |
5.9.2. Konfiguration
Empfohlene Lektüre: Abschnitt 3.1.5 von [ref1]
Die Integration DAO / JPA wird über die Spring-Datei [spring-config-dao.xml] und die Datei JPA [persistence.xml] konfiguriert:
![]() |
Frage: Geben Sie den Inhalt dieser beiden Dateien an. Es wird davon ausgegangen, dass es sich bei der verwendeten Datenbank um die Datenbank „MySQL5 [dbpam_hibernate]“ handelt, die durch das Skript „SQL [dbpam_hibernate.sql]“ generiert wurde. Die Spring-Datei definiert die folgenden drei Beans: employeDao vom Typ EmployeDao, indemniteDao vom Typ IndemniteDao, cotisationDao vom Typ CotisationDao. Außerdem wird als Implementierung für JPA die Implementierung Hibernate verwendet.
5.9.3. Tests
Empfohlene Lektüre: Abschnitte 3.1.6 und 3.1.7 von [ref1]
Nachdem die Schicht [DAO] nun geschrieben und konfiguriert ist, können wir sie testen. Die Testarchitektur sieht wie folgt aus:
![]() |
5.9.4. InitDB
Wir werden zwei Testprogramme für die Schicht [DAO] erstellen. Diese werden im Paket [dao] [2] des Zweigs [Test Packages] [1] des NetBeans-Projekts abgelegt. Dieser Zweig ist nicht in dem durch die Option [Build project] generierten Projekt enthalten, wodurch sichergestellt ist, dass die Testprogramme, die wir dort ablegen, nicht in die endgültige .jar-Datei des Projekts aufgenommen werden.
![]() |
Die im Zweig [Test Packages] platzierten Klassen haben Zugriff auf die Klassen im Zweig [Source Packages] sowie auf die Klassenbibliotheken des Projekts. Falls die Tests andere Bibliotheken als die des Projekts benötigen, müssen diese in den Zweigen [Test Libraries] und [2] deklariert werden.
Die Testklassen verwenden das Unit-Test-Tool JUnit:
- [JUnitInitDB] führt keine Tests durch. Es füllt die Datenbank mit einigen Datensätzen und zeigt diese anschließend auf der Konsole an.
- [JUnitDao] führt eine Reihe von Tests durch und überprüft deren Ergebnis.
Das Grundgerüst der Klasse [JUnitInitDB] sieht wie folgt aus:
- Die Methode [init] wird vor Beginn der Testreihe ausgeführt (Annotation @BeforeClass). Sie instanziiert die Schicht [DAO].
- Die Methode [clean] wird vor jedem Test ausgeführt (Annotation @Before). Sie leert die Datenbank.
- Die Methode [initDB] ist ein Test (Annotation @Test). Es ist der einzige. Ein Test muss Assertion-Anweisungen Assert.assertCondition enthalten. Hier wird es keine geben. Die Methode ist also ein Scheintest. Ihre Aufgabe besteht darin, die Datenbank mit einigen Zeilen zu füllen und anschließend den Inhalt der Datenbank auf der Konsole anzuzeigen. Hier kommen die Methoden create und findAll aus den Schichten [DAO] zum Einsatz.
Aufgabe: Vervollständigen Sie den Code der Klasse [JUnitInitDB]. Orientieren Sie sich dabei am Beispiel aus Abschnitt 3.1.6 von [ref1]. Der Code generiert den in Abschnitt 5.1 dargestellten Inhalt.
5.9.5. Implementier e der Tests
Wir sind nun bereit, [InitDB] auszuführen. Wir beschreiben die Vorgehensweise anhand von SGBD und MySQL5:
![]() |
- Die Klassen [1], die Konfigurationsdateien [2] und die Testklassen der Schicht [DAO] sowie [3] sind eingerichtet,
![]() |
- das Projekt wird erstellt: [4]
- die Klasse [JUnitInitDB] wird ausgeführt ([5]). SGBD und MySQL5 werden mit einer bestehenden Basis ([dbpam_hibernate]) gestartet,
- das Fenster [Test Results] [6] zeigt an, dass die Tests erfolgreich waren. Diese Meldung ist hier nicht relevant, da das Programm [JUnitInitDB] keine Assertion-Anweisungen Assert.assertCondition enthält, die zum Fehlschlagen des Tests führen könnten. Dennoch zeigt dies, dass bei der Ausführung des Tests keine Ausnahme aufgetreten ist.
Das Fenster [Output] enthält die Ausführungsprotokolle, die Spring-Protokolle und die Protokolle des Tests selbst. Die von der Klasse [JUnitInitDB] ausgegebenen Informationen lauten wie folgt:
Die Tabellen [EMPLOYES, INDEMNITES, COTISATIONS] wurden gefüllt. Dies lässt sich über eine NetBeans-Verbindung zur Datenbank [dbpam_hibernate] überprüfen.
![]() |
- In [1], auf der Registerkarte [services], werden die Daten der Tabelle [employes] der Verbindung [dbpam_hibernate] [2] angezeigt,
- in [3] das Ergebnis.
5.9.6. JUnitDao
Wir befassen uns nun mit einer zweiten Testklasse [JUnitDao]:
![]() |
Das Grundgerüst der Klasse sieht wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | |
In der vorherigen Testklasse wird die Datenbank vor jedem Test geleert.
Aufgabe: Schreiben Sie die folgenden Methoden:
1 – test02: Man orientiert sich an test01
2 – test03: Ein Mitarbeiter hat ein Feld vom Typ Indemnite. Es müssen daher eine Entität Indemnite und eine Entität Employe angelegt werden
3 – test04.
Wenn man genauso vorgeht wie bei der Testklasse [JUnitInitDB], erhält man folgende Ergebnisse:
![]() |
- In [1] wird die Testklasse
- bei [2] werden die Testergebnisse im Fenster [Test Results] angezeigt
Lösen wir einen Fehler aus, um zu sehen, wie dieser auf der Ergebnisseite angezeigt wird:
Zeile 13: Die Assertion löst einen Fehler aus, da der Wert von Csgrds 3,49 beträgt (Zeile 8). Die Ausführung der Testklasse liefert folgende Ergebnisse:
![]() |
- Die Ergebnisseite [1] zeigt nun an, dass einige Tests fehlgeschlagen sind.
- In [2] findet sich eine Zusammenfassung der Ausnahme, die zum Scheitern des Tests geführt hat. Dort ist die Zeilennummer des Java-Codes angegeben, in der die Ausnahme aufgetreten ist.
5.10. Die Schicht [metier] der Anwendung [PAM]
Nachdem die Schicht [DAO] nun geschrieben wurde, wenden wir uns der Untersuchung der Geschäftssschicht [2] zu:
![]() |
5.10.1. Die Java-Schnittstelle [IMetier]
Diese wurde in Abschnitt 5.7 beschrieben. Wir fassen sie im Folgenden noch einmal zusammen:
Die Implementierung der Schicht [metier] erfolgt in einem Paket [metier]:
![]() |
Das Paket [metier] wird neben der Schnittstelle [IMetier] und ihrer Implementierung [Metier] zwei weitere Klassen: [FeuilleSalaire] und [ElementsSalaire]. Die Klasse [FeuilleSalaire] wurde in Abschnitt 5.7 kurz vorgestellt. Wir kommen nun darauf zurück.
5.10.2. Die Klasse [FeuilleSalaire]
Die Methode [calculerFeuilleSalaire] der Schnittstelle [IMetier] gibt ein Objekt vom Typ [FeuilleSalaire] zurück, das die verschiedenen Elemente einer Gehaltsabrechnung darstellt. Ihre Definition lautet wie folgt:
- Zeile 7: Die Klasse implementiert die Schnittstelle Serializable, da ihre Instanzen über das Netzwerk ausgetauscht werden können.
- Zeile 9: Der Mitarbeiter, auf den sich die Gehaltsabrechnung bezieht
- Zeile 10: die verschiedenen Beitragssätze
- Zeile 11: die verschiedenen Zulagen in Abhängigkeit vom Index des Mitarbeiters
- Zeile 12: die Bestandteile seines Gehalts
- Zeilen 14–22: die beiden Konstruktoren der Klasse
- Zeilen 25–27: Methode [toString] zur Identifizierung eines bestimmten Objekts [FeuilleSalaire]
- Zeilen 29 und folgende: die öffentlichen Zugriffsrechte auf die privaten Felder der Klasse
Die Klasse [ElementsSalaire], auf die in Zeile 11 der oben genannten Klasse [FeuilleSalaire] verwiesen wird, fasst die Bestandteile einer Gehaltsabrechnung zusammen. Ihre Definition lautet wie folgt:
- Zeile 3: Die Klasse implementiert die Schnittstelle Serializable, da sie eine Komponente der Klasse FeuilleSalaire ist, die serialisierbar sein muss.
- Zeile 6: das Grundgehalt
- Zeile 7: die auf dieses Grundgehalt gezahlten Sozialbeiträge
- Zeile 8: die täglichen Unterhaltszahlungen für das Kind
- Zeile 9: die täglichen Verpflegungszuschüsse für das Kind
- Zeile 10: das an die Tagesmutter zu zahlende Nettogehalt
- Zeilen 12–24: Die Konstruktoren der Klasse
- Zeilen 27–31: Methode [toString] zur Identifizierung eines bestimmten Objekts [ElementsSalaire]
- Zeilen 34 und folgende: öffentliche Zugriffsmethoden auf die privaten Felder der Klasse
5.10.3. Die Implementierungsklasse [Metier] der Schicht [metier]
Die Implementierungsklasse [Metier] der Schicht [metier] könnte wie folgt aussehen:
- Zeile 5: Die Spring-Annotation @Transactional sorgt dafür, dass jede Methode der Klasse innerhalb einer Transaktion ausgeführt wird.
- Zeilen 9–10: Die Verweise auf die Schichten [DAO] der Entitäten [Cotisation, Employe, Indemnite]
- Zeilen 14–17: Die Methode [calculerFeuilleSalaire]
- Zeilen 20–22: die Methode [findAllEmployes]
- Zeile 24 und folgende: die öffentlichen Zugriffsmethoden für die privaten Felder der Klasse
Aufgabe: Schreiben Sie den Code für die Methode [findAllEmployes].
Aufgabe: Schreiben Sie den Code für die Methode [calculerFeuilleSalaire].
Beachten Sie dabei folgende Punkte:
- Die Berechnungsweise des Gehalts wurde in Abschnitt 5.2 erläutert.
- Wenn der Parameter [SS] keinem Mitarbeiter entspricht (die Ebene [DAO] hat einen Zeiger null zurückgegeben), löst die Methode eine Ausnahme vom Typ [PamException] mit einem entsprechenden Fehlercode aus.
5.10.4. Tests der Ebene [metier]
Wir erstellen zwei Testprogramme:
![]() |
Die Testklassen [3] werden im Paket [metier] [2] des Zweigs [Test Packages] [1] des Projekts angelegt.
Die Klasse [JUnitMetier_1] könnte wie folgt lauten:
In der Klasse gibt es keine Assertion Assert.assertCondition. Es sollen lediglich einige Gehälter berechnet werden, um sie anschließend manuell zu überprüfen. Die durch die Ausführung der vorherigen Klasse erzeugte Bildschirmausgabe sieht wie folgt aus:
- Zeile 4: die Gehaltsabrechnung von Justine Laverti
- Zeile 5: die Gehaltsabrechnung von Marie Jouveinal
- Zeile 6: Die Ausnahme, die darauf zurückzuführen ist, dass der Mitarbeiter mit der Nummer SS „xx“ nicht existiert.
Frage: Zeile 17 von [JUnitMetier_1] verwendet den Spring-Bean mit dem Namen metier. Geben Sie die Definition dieses Beans in der Datei [spring-config-metier-dao.xml] an.
Die Klasse [JUnitMetier_2] könnte wie folgt aussehen:
Die Klasse [JUnitMetier_2] ist eine Kopie der Klasse [JUnitMetier_1], wobei diesmal Assertions in die Methode test01 eingefügt wurden.
Aufgabe: Schreiben Sie die Methode test01.
Bei der Ausführung der Klasse [JUnitMetier_2] erhält man, wenn alles gut geht, folgende Ergebnisse:

5.11. Die Schicht [ui] der Anwendung [PAM] – Version -Konsole
Nachdem nun die Schicht [metier] geschrieben wurde, müssen wir noch die Schichten [ui] und [1] schreiben:
![]() |
Wir werden zwei verschiedene Implementierungen der Schicht [ui] erstellen: eine Version console und eine grafische Version swing:
![]() |
5.11.1. Die Klasse [ui.console.Main]
Zunächst betrachten wir die Konsolenanwendung, die durch die oben genannte Klasse [ui.console.Main] implementiert wird. Ihre Funktionsweise wurde in Abschnitt 5.3 beschrieben. Das Grundgerüst der Klasse [Main] könnte wie folgt aussehen:
Aufgabe: Vervollständigen Sie den obigen Code.
5.11.2. Ausführung
Um die Klasse [ui.console.Main] auszuführen, gehen Sie wie folgt vor:
![]() |
- in [1], wählen Sie die Projekteigenschaften aus,
- in [2] die Projekteigenschaft [Run] auswählen,
- verwenden Sie die Schaltfläche [3], um die auszuführende Klasse (die sogenannte Hauptklasse) festzulegen,
- wählen Sie die Klasse [4] aus,
- die Klasse erscheint in [5]. Diese benötigt drei Argumente zur Ausführung (Nr. SS, Anzahl der gearbeiteten Stunden, Anzahl der gearbeiteten Tage). Diese Argumente werden in [6] eingegeben,
- Anschließend kann das Projekt [7] ausgeführt werden. Aufgrund der vorstehenden Konfiguration wird die Klasse [ui.console.Main] ausgeführt.
Die Ergebnisse der Ausführung werden im Fenster [output] angezeigt:
![]() | ![]() |
5.12. Die Schicht [ui] der Anwendung [PAM] – grafische Version
Wir implementieren nun die Schicht [ui] mit einer grafischen Benutzeroberfläche:
![]() |
![]() |
- in [1], die Klasse [PamJFrame] der grafischen Benutzeroberfläche
- in [2]: die grafische Benutzeroberfläche
5.12.1. Eine kurze Anleitung
Um die grafische Benutzeroberfläche zu erstellen, kann man wie folgt vorgehen:
![]() |
- [1]: Mit der Schaltfläche [1] wird eine neue Datei erstellt. [New File...]
- [2]: Wählen Sie die Kategorie der Datei aus: [Swing GUI Forms], c.a.d. Grafische Formulare
- [3]: Wählen Sie den Typ [JFrame Form], einen leeren Formulartyp
![]() |
- [5]: Man gibt dem Formular einen Namen, der gleichzeitig als Klasse dient
- [6]: Das Formular wird in ein Paket eingefügt
- [8]: Das Formular wird zur Projektstruktur hinzugefügt
- [9]: Das Formular ist über zwei Perspektiven zugänglich: [Design] und [9], über die die verschiedenen Komponenten des Formulars entworfen werden können, sowie [Source] und [10 ci-dessous], die Zugriff auf den Java-Code des Formulars gewähren. Letztendlich ist ein Formular eine Java-Klasse wie jede andere auch. Die Perspektive [Design] dient als Hilfsmittel zum Entwerfen des Formulars. Bei jedem Hinzufügen einer Komponente im Modus [Design] wird in der Perspektive [Source] Java-Code hinzugefügt, um diese Komponente zu berücksichtigen.
![]() |
- [11]: Die Liste der für ein Formular verfügbaren Swing-Komponenten finden Sie im Fenster [Palette].
- [12]: Das Fenster [Inspector] zeigt die Baumstruktur der Formularkomponenten an. Komponenten mit einer visuellen Darstellung befinden sich im Zweig [JFrame], die übrigen im Zweig [Other Components].
![]() |
- In [13] wählen wir eine Komponente [JLabel] mit einem einfachen Klick aus
- in [14] legen wir sie im Modus [Design] auf das Formular ab
- in [15] legen wir die Eigenschaften von JLabel fest (Text, Schriftart).
![]() |
- in [16], das erzielte Ergebnis.
- In [17] wird die Vorschau des Formulars angefordert
- in [18], das Ergebnis
- in [19] wurde das Label [JLabel1] zur Komponentenbaumstruktur im Fenster [Inspector] hinzugefügt
![]() |
- in [20] und [21]: In der Formularperspektive [Source] wurde Java-Code hinzugefügt, um das hinzugefügte JLabel zu verwalten.
Ein Tutorial zur Erstellung von Formularen mit NetBeans ist unter der URL [http://www.netbeans.org/kb/trails/matisse.html] verfügbar.
5.12.2. Die grafische Benutzeroberfläche [PamJFrame]
Wir werden die folgende grafische Benutzeroberfläche erstellen:
![]() |
- in [1], die grafische Benutzeroberfläche
- in [2], die Baumstruktur ihrer Komponenten: ein JLabel und sechs Container JPanel
JLabel1
![]() |
JPanel1
![]() | ![]() |
JPanel2
![]() | ![]() |
JPanel3
![]() | ![]() |
JPanel4
![]() | ![]() |
JPanel5
![]() | ![]() |
Praxisaufgabe: Erstellen Sie die oben gezeigte grafische Benutzeroberfläche mithilfe des Tutorials [http://www.netbeans.org/kb/trails/matisse.html].
5.12.3. Ereignisse der grafischen Benutzeroberfläche
Literaturempfehlung: Kapitel [Interfaces graphiques] aus [ref2].
Wir werden den Klick auf die Schaltfläche [jButtonSalaire] verarbeiten. Um die Methode zur Verarbeitung dieses Ereignisses zu erstellen, können wir wie folgt vorgehen:
![]() |
Der Handler für den Klick auf die Schaltfläche [JButtonSalaire] wird generiert:
Der Java-Code, der die vorstehende Methode mit dem Klick auf die Schaltfläche „[JButtonSalaire]“ verknüpft, wird ebenfalls generiert:
Die Zeilen 2–5 geben an, dass der Klick (evt vom Typ ActionPerformed) auf die Schaltfläche [jButtonSalaire] (Zeile 2) durch die Methode [jButtonSalaireActionPerformed] (Zeile 4) verarbeitet werden soll.
Wir werden außerdem das Ereignis [caretUpdate] (Verschiebung des Eingabecursors) im Eingabefeld [jTextFieldHT] verarbeiten. Um den Handler für dieses Ereignis zu erstellen, gehen wir wie zuvor vor:
![]() |
Der Handler für das Ereignis [caretUpdate] im Eingabefeld [jTextFieldHT] wird generiert:
Der Java-Code, der die vorherige Methode dem Ereignis [caretUpdate] im Eingabefeld [jTextFieldHT] zuordnet, wird ebenfalls generiert:
Die Zeilen 1–4 geben an, dass das Ereignis [caretUpdate] (Zeile 2) auf der Schaltfläche [jTextFieldHT] (Zeile 1) von der Methode [ jTextFieldHTCaretUpdate] (Zeile 3) verarbeitet werden soll.
5.12.4. Initialisierung der grafischen Benutzeroberfläche
Kehren wir zur Architektur unserer Anwendung zurück:
![]() |
Die Schicht [ui] benötigt eine Referenz auf die Schicht [metier]. Erinnern wir uns daran, wie diese Referenz in der Anwendung console abgerufen wurde:
In der grafischen Anwendung ist die Vorgehensweise dieselbe. Bei deren Initialisierung muss auch die Referenz [IMetier metier] aus Zeile 3 oben initialisiert werden. Der für die grafische Benutzeroberfläche generierte Code lautet derzeit wie folgt:
- Zeilen 29–35: Die statische Methode [main], die die Anwendung startet
- Zeile 32: Eine Instanz der grafischen Benutzeroberfläche [PamJFrame] wird erstellt und sichtbar gemacht.
- Zeilen 7–9: Der Konstruktor der grafischen Benutzeroberfläche.
- Zeile 8: Aufruf der in Zeile 17 definierten Methode [initComponents]. Diese Methode wird automatisch auf der Grundlage der im Modus [Design] durchgeführten Arbeit generiert. Sie darf nicht verändert werden.
- Zeile 21: Die Methode, die die Bewegung des Eingabecursors im Feld [jTextFieldHT] steuert
- Zeile 25: Die Methode, die den Klick auf die Schaltfläche [jButtonSalaire] steuert
Um dem obigen Code unsere eigenen Initialisierungen hinzuzufügen, können wir wie folgt vorgehen:
- Zeile 4: Wir rufen eine benutzerdefinierte Methode auf, um unsere eigenen Initialisierungen durchzuführen. Diese sind im Code der Zeilen 10–42 definiert.
Frage: Vervollständigen Sie mithilfe der Kommentare den Code der Prozedur [doMyInit].
5.12.5. Ereignisbehandler
Frage: Schreiben Sie die Methode [jTextFieldHTCaretUpdate]. Diese Methode muss sicherstellen, dass die Schaltfläche [jButtonSalaire] deaktiviert ist, wenn der Wert im Feld [jTextFieldHT] keine reelle Zahl >= 0 ist.
Aufgabe: Schreiben Sie die Methode [jButtonSalaireActionPerformed], die die Gehaltsabrechnung des in [jComboBoxEmployes] ausgewählten Mitarbeiters anzeigen soll.
5.12.6. Ausführung der grafischen Benutzeroberfläche
Um die grafische Benutzeroberfläche auszuführen, ändern wir die Konfiguration [Run] des Projekts:
![]() |
- in [1] ändern und die Klasse der grafischen Benutzeroberfläche angeben
Das Projekt muss vollständig sein und die Konfigurationsdateien (persistence.xml, spring-config-metier-dao.xml) sowie die Klasse der grafischen Benutzeroberfläche enthalten. Starten Sie das Ziel SGBD, bevor Sie das Projekt ausführen.
5.13. Implementierung der Schicht JPA mit EclipseLink
Wir betrachten die folgende Architektur, in der die Schicht JPA nun durch EclipseLink implementiert wird:
![]() |
5.13.1. Das NetBeans-Projekt
Das neue NetBeans-Projekt wird durch Kopieren des vorherigen Projekts erstellt:
![]() |
- in [1]: Nach einem Rechtsklick auf das Hibernate-Projekt wählen Sie Copy
- und über die Schaltfläche [2] den übergeordneten Ordner für das neue Projekt auswählen. Der Name des Ordners erscheint in [3].
- In [4] geben Sie dem neuen Projekt einen Namen
- Geben Sie unter [5] den Namen des Projektordners ein
![]() |
- in [1] wurde das neue Projekt erstellt. Es trägt denselben Namen wie das Original,
- in [2] und [3] wird es in [mv-pam-spring-eclipselink] umbenannt.
Das Projekt muss an zwei Stellen geändert werden, um es an die neue Schicht JPA / EclipseLink anzupassen:
- In [4] müssen die Spring-Konfigurationsdateien geändert werden. Dort ist nämlich die Konfiguration der Ebene JPA enthalten.
- In [5] müssen die Projektbibliotheken angepasst werden: Die Hibernate-Bibliotheken müssen durch die von EclipseLink ersetzt werden.
Beginnen wir mit diesem letzten Punkt. Die Datei [pom.xml] für das neue Projekt sieht wie folgt aus:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st</groupId>
<artifactId>mv-pam-spring-eclipselink</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-pam-spring-eclipselink</name>
<url>http://maven.apache.org</url>
<repositories>
<repository>
<url>http://repo1.maven.org/maven2/</url>
<id>swing-layout</id>
<layout>default</layout>
<name>Repository for library Library[swing-layout]</name>
</repository>
<repository>
<url>http://download.eclipse.org/rt/eclipselink/maven.repo/</url>
<id>eclipselink</id>
<layout>default</layout>
<name>Repository for library Library[eclipselink]</name>
</repository>
</repositories>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
<type>jar</type>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.1.1.RELEASE</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>javax.persistence</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.swinglabs</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.3</version>
</dependency>
</dependencies>
</project>
- Zeilen 73–82: die Abhängigkeiten für die Implementierung JPA und EclipseLink,
- Zeilen 19–24: das Maven-Repository für EclipseLink.
Die Spring-Konfigurationsdateien müssen geändert werden, um anzugeben, dass sich die Implementierung JPA geändert hat. In beiden Dateien ändert sich nur der Abschnitt, der die Schicht JPA konfiguriert. In [spring-config-metier-dao.xml] steht beispielsweise:
Die Zeilen 19–36 konfigurieren die Schicht JPA. Die verwendete Implementierung JPA ist Hibernate (Zeile 22). Außerdem ist die Zieldatenbank [dbpam_hibernate] (Zeile 41).
Um zu einer Implementierung JPA / EclipseLink zu wechseln, werden die oben genannten Zeilen 19–35 durch die folgenden Zeilen ersetzt:
- Zeile 5: Die verwendete Implementierung JPA ist EclipseLink
- Zeile 9: Die Eigenschaft databasePlatform legt das Ziel SGBD fest, hier MySQL
- Zeile 11: Zum Generieren der Datenbanktabellen, wenn die Schicht JPA instanziiert wird. Hier ist die Eigenschaft auskommentiert.
- Zeile 7: Zur Anzeige der von der Schicht JPA ausgegebenen Befehle SQL in der Konsole. Hier ist die Eigenschaft auskommentiert.
Außerdem lautet die Zieldatenbank nun [dbpam_eclipselink] (Zeile 4 unten):
5.13.2. Durchführung der Tests
Bevor die gesamte Anwendung getestet wird, sollte überprüft werden, ob die Tests JUnit mit der neuen Implementierung JPA erfolgreich durchlaufen werden. Zuvor werden zunächst die Tabellen aus der Datenbank gelöscht. Dazu wird bei Bedarf auf der Registerkarte [Runtime] in NetBeans eine Verbindung zur Datenbank dbpam_eclipselink / MySQL5 hergestellt. Sobald die Verbindung zur Datenbank dbpam_eclipselink / MySQL5 hergestellt ist, können die Tabellen wie unten gezeigt gelöscht werden:
- [1]: vor dem Löschen
- [2]: nach dem Löschen
![]() |
Anschließend kann der erste Test auf der Ebene [DAO]: InitDB durchgeführt werden, der die Datenbank füllt. Damit die zuvor gelöschten Tabellen von der Anwendung neu angelegt werden, muss sichergestellt werden, dass in der Spring-Konfiguration JPA / EclipseLink die Zeile:
vorhanden ist und nicht auskommentiert wurde.
Wir erstellen das Projekt (Build) und führen anschließend den Test [JUnitInitDB] „ “ aus:
![]() |
- in [1] wird der Test InitDB ausgeführt.
- Bei [2] schlägt er fehl. Die Ausnahme wird von Spring ausgelöst und nicht durch einen fehlgeschlagenen Test.
Ursache: org.springframework.beans.factory.BeanCreationException: Fehler beim Erstellen des Beans mit dem Namen „entityManagerFactory“, der in der Klassenpfad-Ressource [spring-config-DAO.xml] definiert ist: Der Aufruf der init-Methode ist fehlgeschlagen; die verschachtelte Ausnahme lautet java.lang.IllegalStateException: Um InstrumentationLoadTimeWeaver zu verwenden, muss der Java-Agent gestartet werden. Siehe Spring-Dokumentation.
Spring weist auf ein Konfigurationsproblem hin. Die Meldung ist unklar. Der Grund für die Ausnahme wurde in Abschnitt 3.1.9 von [ref1] erläutert. Damit die Spring-/EclipseLink-Konfiguration funktioniert, muss JVM, das die Anwendung ausführt, mit einem bestimmten Parameter – einem Java-Agenten – gestartet werden. Dieser Parameter hat folgende Form:
[spring-agent.jar] ist der Java-Agent, den JVM benötigt, um die Spring-Konfiguration / EclipseLink zu verwalten.
Beim Ausführen eines Projekts können Argumente an den JVM übergeben werden:
![]() |
- in [1] greift man auf die Projekteigenschaften zu
- in [2] werden die Eigenschaften von Run angezeigt
- in [3] wird der Parameter -javaagent an JVM übergeben
5.13.3. InitDB
Nun sind wir bereit, [InitDB] erneut zu testen. Diesmal sind folgende Ergebnisse zu verzeichnen:
![]() |
- in [1] war der Test erfolgreich
- bei [2] wird auf der Registerkarte [Services] die Verbindung von NetBeans zur Datenbank [dbpam_eclipselink] aktualisiert
- in [3] wurden vier Tabellen angelegt
![]() |
- In [5] wird der Inhalt der Tabelle [employes] angezeigt
- in [6] wird das Ergebnis angezeigt.
5.13.4. JUnitDao
Die Ausführung der Testklasse [JUnitDao] kann fehlschlagen, obwohl sie mit der Implementierung JPA / Hibernate erfolgreich war. Um zu verstehen, warum das so ist, betrachten wir ein Beispiel.
Die getestete Methode ist die folgende Methode IndemniteDao.create:
- Zeilen 15–22: die getestete Methode
Die Testmethode lautet wie folgt:
package dao;
...
public class JUnitDao {
// Ebenen DAO
static private IEmployeDao employeDao;
static private IIndemniteDao indemniteDao;
static private ICotisationDao cotisationDao;
@BeforeClass
public static void init() {
// Protokoll
log("init");
// Anwendungskonfiguration
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-DAO.xml");
// Ebenen DAO
employeDao = (IEmployeDao) ctx.getBean("employeDao");
indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
}
@Before()
public void clean() {
// Datenbank leeren
for (Employe employe : employeDao.findAll()) {
employeDao.destroy(employe);
}
for (Cotisation cotisation : cotisationDao.findAll()) {
cotisationDao.destroy(cotisation);
}
for (Indemnite indemnite : indemniteDao.findAll()) {
indemniteDao.destroy(indemnite);
}
}
// Protokolle
private static void log(String message) {
System.out.println("----------- " + message);
}
// Tests
….
@Test
public void test05() {
log("test05");
// Es werden zwei Entschädigungen mit demselben Index angelegt
// Verstößt gegen die Eindeutigkeitsbedingung des Index
boolean erreur = true;
Indemnite indemnite1 = null;
Indemnite indemnite2 = null;
Throwable th = null;
try {
indemnite1 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
indemnite2 = indemniteDao.create(new Indemnite(1, 1.93, 2, 3, 12));
erreur = false;
} catch (PamException ex) {
th = ex;
// Prüfungen
Assert.assertEquals(31, ex.getCode());
} catch (Throwable th1) {
th = th1;
}
// Prüfungen
Assert.assertTrue(erreur);
// Ausnahmefolge
System.out.println("Chaîne des exceptions --------------------------------------");
System.out.println(th.getClass().getName());
while (th.getCause() != null) {
th = th.getCause();
System.out.println(th.getClass().getName());
}
// Die erste Entschädigung musste beibehalten werden
Indemnite indemnite = indemniteDao.find(indemnite1.getId());
// Überprüfung
Assert.assertNotNull(indemnite);
Assert.assertEquals(1, indemnite.getIndice());
Assert.assertEquals(1.93, indemnite.getBaseHeure(), 1e-6);
Assert.assertEquals(2, indemnite.getEntretienJour(), 1e-6);
Assert.assertEquals(3, indemnite.getRepasJour(), 1e-6);
Assert.assertEquals(12, indemnite.getIndemnitesCP(), 1e-6);
// Die zweite Entschädigung durfte nicht beibehalten werden
List<Indemnite> indemnites = indemniteDao.findAll();
int nbIndemnites = indemnites.size();
Assert.assertEquals(nbIndemnites, 1);
}
...
}
Frage: Erläutern Sie, was der Test test05 bewirkt, und geben Sie die erwarteten Ergebnisse an.
Die mit einer JPA-/Hibernate-Schicht erzielten Ergebnisse lauten wie folgt:
Der Test wird bestanden (c.a.d). Die Assertions werden überprüft, und es wird keine Ausnahme aus der Testmethode ausgelöst.
Frage: Erläutern Sie, was passiert ist.
Die mit den Schichten JPA / EclipseLink erzielten Ergebnisse lauten wie folgt:
Wie zuvor bei Hibernate wird der Test bestanden, c.a.d. Die Assertions werden überprüft und es wird keine Ausnahme aus der Testmethode ausgelöst.
Frage: Erläutern Sie, was passiert ist.
Frage: Was lässt sich aus diesen beiden Beispielen über die Austauschbarkeit der Implementierungen JPA schließen? Ist sie hier vollständig gegeben?
5.13.5. Die weiteren Tests
Sobald die Schicht [DAO] getestet und als korrekt befunden wurde, kann man mit den Tests der Schicht [metier] und denen des Projekts selbst in seiner Konsolen- oder grafischen Version fortfahren. Die Änderung der Implementierung JPA hat keinerlei Auswirkungen auf die Schichten [metier] und [ui]; wenn diese Schichten also mit Hibernate funktionierten, werden sie auch mit EclipseLink funktionieren, mit wenigen Ausnahmen: Das vorstehende Beispiel zeigt nämlich, dass die von den Schichten [DAO] ausgelösten Ausnahmen abweichen können. So löst beispielsweise bei Verwendung des Tests Spring / JPA / Hibernate eine Ausnahme vom Typ [PamException] aus, eine anwendungsspezifische Ausnahme [pam], während Spring / JPA / EclipseLink hingegen eine Ausnahme vom Typ [TransactionSystemException] auslöst, eine Ausnahme des Spring-Frameworks. Wenn im Testfall die Schicht [ui] eine Ausnahme vom Typ [PamException] erwartet, weil sie mit Hibernate erstellt wurde, funktioniert sie nicht mehr, sobald auf EclipseLink umgestellt wird.
5.13.6. Aufgabe
Praktische Aufgabe: Führen Sie die Tests der Anwendungen console und swing mit verschiedenen SGBD erneut durch: MySQL5, Oracle XE, SQL-Server.





















































































