Skip to content

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:

Tabelle EMPLOYES: enthält Informationen zu den verschiedenen Tagesmüttern

Struktur:

ID
Primärschlüssel
VERSION
Versionsnummer – erhöht sich bei jeder Änderung der Zeile
SS
Sozialversicherungsnummer des Mitarbeiters – eindeutig
NOM
Name des Mitarbeiters
prenom
sein Vorname
ADRESSE
seine Adresse
VILLE
seine Stadt
CODEPOSTAL
seine Postleitzahl
INDEMNITE_ID
Fremdschlüssel im Feld [ID] der Tabelle [INDEMNITES]

Sein Inhalt könnte wie folgt lauten:

Image

Tabelle COTISATIONS: enthält die für die Berechnung der Sozialbeiträge erforderlichen Prozentsätze

Struktur:

ID
Primärschlüssel
VERSION
Versionsnummer – erhöht sich bei jeder Änderung der Zeile
CSGRDS
Prozentsatz: allgemeiner Sozialbeitrag + Beitrag zur Tilgung der Sozialschuld
CSGD
Prozentsatz: abzugsfähiger allgemeiner Sozialbeitrag
SECU
Prozentsatz: Sozialversicherung, Witwenrente, Altersrente
RETRAITE
Prozentsatz: Zusatzrente + Arbeitslosenversicherung

Der Inhalt könnte wie folgt lauten:

Image

Die Sozialversicherungsbeiträge sind unabhängig vom Arbeitnehmer. Die vorstehende Tabelle enthält nur eine Zeile.

Tabelle INDEMNITES: enthält die Elemente, die zur Berechnung des zu zahlenden Gehalts erforderlich sind.
ID
Primärschlüssel
 
VERSION
Versionsnummer – erhöht sich bei jeder Änderung der Zeile
 
INDICE
Verarbeitungsindex – eindeutig
 
BASEHEURE
Nettopreis in Euro für eine Stunde Bereitschaftsdienst
 
ENTRETIENJOUR
Verpflegungszuschlag in Euro pro Betreuungs-Tag
 
REPASJOUR
Verpflegungszulage in Euro pro Betreuungs-Tag
 
INDEMNITESCP
Urlaubsgeld. Es handelt sich um einen Prozentsatz, der auf das Grundgehalt anzuwenden ist.
 
  

Der Inhalt könnte wie folgt lauten:

Image

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:

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

[TOTALJOURS]: total des jours travaillés
dans le mois
[TOTALHEURES]=150
[TOTALJOURS]= 20
Das Grundgehalt der Tagesmutter
ergibt sich aus der folgenden Formel:
[SALAIREBASE]=([TOTALHEURES]
*[BASEHEURE])*(1+
[INDEMNITESCP]/100)
[SALAIREBASE]=
(150*[2.1])*(1+0.15)= 362,25
Ein bestimmter Betrag an Sozialbeiträgen
müssen von diesem Grundgehalt
abgezogen werden:

Contribution sociale généralisée et
contribution au remboursement de la
dette sociale :
 [SALAIREBASE]*[CSGRDS/100]

Contribution sociale généralisée déductible :
 [SALAIREBASE]*[CSGD/100]

Sécurité sociale, veuvage, vieillesse :
 [SALAIREBASE]*[SECU/100]

Retraite Complémentaire + AGPF +
Assurance Chômage :
 [SALAIREBASE]*[RETRAITE/100]
CSGRDS : 12,64
CSGD : 22,28
Sécurité sociale : 34,02
Retraite : 28,55
Gesamtsumme der Sozialbeiträge:
[COTISATIONSSOCIALES]=
[SALAIREBASE]*(CSGRDS+CSGD
+SECU+RETRAITE)/100
[COTISATIONSSOCIALES]=97,48
Darüber hinaus hat die Tagesmutter für jeden gearbeiteten Tag Anspruch auf eine Aufwandsentschädigung sowie eine Verpflegungszulage. In diesem Zusammenhang erhält sie folgende Zulagen:

[Indemnités]=[TOTALJOURS]
*(ENTRETIENJOUR+REPASJOUR)
[INDEMNITES]=104
Insgesamt ergibt sich folgender Nettolohn, der an die Tagesmutter zu zahlen ist:
[SALAIREBASE]-[COTISATIONSSOCIALES]+[INDEMNITÉS]
[salaire NET]=368,77

5.3. Funktionsweise der Konsolenanwendung

Hier ist ein Beispiel für die Ausführung der Konsolenanwendung in einem DOS-Fenster:

dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20

Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

Informations Employé :
Nom : Jouveinal
Prénom : Marie
Adresse : 5 rue des Oiseaux
Ville : St Corentin
Code Postal : 49203
Indice : 2

Informations Cotisations :
CSGRDS : 3.49 %
CSGD : 6.15 %
Retraite : 7.88 %
Sécurité sociale : 9.39 %

Informations Indemnités :
Salaire horaire : 2.1 euro
Entretien/jour : 2.1 euro
Repas/jour : 3.1 euro
Congés Payés : 15.0 %

Informations Salaire :
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'entretien : 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

Wir werden ein Programm schreiben, das die folgenden Informationen entgegennimmt:

  1. Sozialversicherungsnummer der Tagesmutter (im Beispiel 254104940426058 – Zeile 1)
  2. Gesamtzahl der gearbeiteten Stunden (im Beispiel 150 – Zeile 1)
  3. 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

Image

Tabelle

Tabelle INDEMNITES

Image

Tabelle

Tabelle COTISATIONS

Image

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:

------------------------------------------------------------------------
Building mv-pam-jpa-hibernate 1.0-SNAPSHOT
------------------------------------------------------------------------

[resources:resources]
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
Copying 1 resource

[compiler:compile]
Nothing to compile - all classes are up to date

[exec:exec]
juin 21, 2012 4:22:47 PM org.hibernate.annotations.common.Version <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
juin 21, 2012 4:22:47 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.1.2}
juin 21, 2012 4:22:47 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
juin 21, 2012 4:22:47 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000402: Using Hibernate built-in connection pool (not for production use!)
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000115: Hibernate connection pool size: 20
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000006: Autocommit mode: true
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000401: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost:3306/dbpam_hibernate]
juin 21, 2012 4:22:48 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
INFO: HHH000046: Connection properties: {user=root, autocommit=true, release_mode=auto}
juin 21, 2012 4:22:48 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
juin 21, 2012 4:22:48 PM org.hibernate.engine.jdbc.internal.LobCreatorBuilder useContextualLobCreation
INFO: HHH000423: Disabling contextual LOB creation as JDBC driver reported JDBC version [3] less than 4
juin 21, 2012 4:22:48 PM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000268: Transaction strategy: org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory
juin 21, 2012 4:22:48 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000227: Running hbm2ddl schema export
Hibernate: 
    alter table EMPLOYES 
        drop 
        foreign key FK75C8D6BC73F24A67
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: HHH000389: Unsuccessful: alter table EMPLOYES drop foreign key FK75C8D6BC73F24A67
juin 21, 2012 4:22:48 PM org.hibernate.tool.hbm2ddl.SchemaExport perform
ERROR: Table 'dbpam_hibernate.employes' doesn't exist
Hibernate: 
    drop table if exists COTISATIONS
Hibernate: 
    drop table if exists EMPLOYES
Hibernate: 
    drop table if exists INDEMNITES
Hibernate: 
    create table COTISATIONS (
        id bigint not null auto_increment,
        CSGD double precision not null,
        CSGRDS double precision not null,
        RETRAITE double precision not null,
        SECU double precision not null,
        VERSION integer not null,
        primary key (id)
    )
Hibernate: 
    create table EMPLOYES (
        id bigint not null auto_increment,
        SS varchar(15) not null unique,
        ADRESSE varchar(50) not null,
        CP varchar(5) not null,
        NOM varchar(30) not null,
        PRENOM varchar(20) not null,
        VERSION integer not null,
        VILLE varchar(30) not null,
        INDEMNITE_ID bigint not null,
        primary key (id)
    )
Hibernate: 
    create table INDEMNITES (
        id bigint not null auto_increment,
        BASE_HEURE double precision not null,
        ENTRETIEN_JOUR double precision not null,
        INDEMNITES_CP double precision not null,
        INDICE integer not null unique,
        REPAS_JOUR double precision not null,
        VERSION integer not null,
        primary key (id)
    )
Hibernate: 
    alter table EMPLOYES 
        add index FK75C8D6BC73F24A67 (INDEMNITE_ID), 
        add constraint FK75C8D6BC73F24A67 
        foreign key (INDEMNITE_ID) 
        references INDEMNITES (id)
juin 21, 2012 4:22:49 PM org.hibernate.tool.hbm2ddl.SchemaExport execute
INFO: HHH000230: Schema export complete
juin 21, 2012 4:22:49 PM org.hibernate.service.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH000030: Cleaning up connection pool [jdbc:mysql://localhost:3306/dbpam_hibernate]
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 2.637s
Finished at: Thu Jun 21 16:22:49 CEST 2012
Final Memory: 8M/153M

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.

Wir werden ein neues Maven-Projekt in der folgenden Umgebung erstellen:

Wir werden die Vorgehensweise aus dem vorherigen Abschnitt befolgen:

  1. Erstellen einer Datenbank mit den Namen MySQL und [dbpam_eclipselink]. Zur Erstellung verwenden wir das Skript [dbpam_eclipselink.sql],
  2. die Projektdatei [persistence.xml] anlegen. Die Implementierung JPA 2.0 EclipseLink übernehmen,
  3. fügen Sie in die generierten Abhängigkeiten die Abhängigkeit des Treibers JDBC von MySQL hinzu,
  4. die Entitäten JPA und das Konsolenprogramm hinzufügen,
  5. 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:

------------------------------------------------------------------------
Building mv-pam-jpa-eclipselink 1.0-SNAPSHOT
------------------------------------------------------------------------

[resources:resources]
[debug] execute contextualize
Using 'UTF-8' encoding to copy filtered resources.
Copying 1 resource

[compiler:compile]
Nothing to compile - all classes are up to date

[exec:exec]
[EL Config]: 2012-06-22 14:35:01.852--ServerSession(730572764)--Thread(Thread[main,5,main])--Der Zugriffstyp für die persistente Klasse [class jpa.Cotisation] ist auf [FIELD] gesetzt.
[EL Config]: 2012-06-22 14:35:01.884--ServerSession(730572764)--Thread(Thread[main,5,main])--Der Zugriffstyp für die persistente Klasse [class jpa.Employe] ist auf [FIELD] festgelegt.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--Die Zielentitätsklasse (Referenzklasse) für das Viele-zu-Eins-Zuordnungselement [field indemnite] wird standardmäßig auf die Klasse jpa.Indemnite gesetzt.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--Der Zugriffstyp für die persistente Klasse [class jpa.Indemnite] ist auf [FIELD] gesetzt.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--Der Aliasname für die Entitätsklasse [class jpa.Cotisation] wird standardmäßig auf „Cotisation“ gesetzt.
[EL Config]: 2012-06-22 14:35:01.915--ServerSession(730572764)--Thread(Thread[main,5,main])--Der Spaltenname für das Element [id] wird standardmäßig auf „ID“ gesetzt.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--Der Aliasname für die Entitätsklasse [class jpa.Employe] wird standardmäßig auf „Employe“ gesetzt.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--Der Spaltenname für das Element [id] wird standardmäßig auf „ID“ gesetzt.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--Der Aliasname für die Entitätsklasse [class jpa.Indemnite] wird standardmäßig auf „Indemnite“ gesetzt.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--Der Spaltenname für das Element [id] wird standardmäßig auf „ID“ gesetzt.
[EL Config]: 2012-06-22 14:35:01.962--ServerSession(730572764)--Thread(Thread[main,5,main])--Der Name der Primärschlüsselspalte für das Mapping-Element [field indemnite] wird standardmäßig auf ID gesetzt.
[EL Info]: 2012-06-22 14:35:02.558--ServerSession(730572764)--Thread(Thread[main,5,main])--EclipseLink, Version: Eclipse Persistence Services – 2.3.0.v20110604-r9504
[EL Config]: 2012-06-22 14:35:02.568--ServerSession(730572764)--Verbindung(1543921451)--Thread(Thread[main,5,main])--Verbindung wird hergestellt(DatabaseLogin(
    platform=>MySQLPlatform
    user name=> "root"
    datasource URL=> "jdbc:mysql://localhost:3306/dbpam_eclipselink"
))
[EL Config]: 2012-06-22 14:35:02.738--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--Verbunden: jdbc:mysql://localhost:3306/dbpam_eclipselink
    User: root@localhost
    Database: MySQL  Version: 5.5.20-log
    Driver: MySQL-AB JDBC Driver  Version: mysql-connector-java-5.1.6 ( Revision: ${svn.Revision} )
[EL Info]: 2012-06-22 14:35:02.798--ServerSession(730572764)--Thread(Thread[main,5,main])--file:/D:/data/istia-1112/netbeans/glassfish/mv-pam/05/mv-pam-jpa-eclipselink/target/classes/_pam-jpa-eclipselinkPU Anmeldung erfolgreich
[EL Fine]: 2012-06-22 14:35:02.818--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES DROP FOREIGN KEY FK_EMPLOYES_INDEMNITE_ID
[EL Fine]: 2012-06-22 14:35:03.088--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--DROP TABLE COTISATIONS
[EL Fine]: 2012-06-22 14:35:03.118--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE COTISATIONS (ID BIGINT NOT NULL, CSGD DOUBLE NOT NULL, CSGRDS DOUBLE NOT NULL, RETRAITE DOUBLE NOT NULL, SECU DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.198--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--DROP TABLE EMPLOYES
[EL Fine]: 2012-06-22 14:35:03.238--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE EMPLOYES (ID BIGINT NOT NULL, SS VARCHAR(15) NOT NULL UNIQUE, ADRESSE VARCHAR(50) NOT NULL, CP VARCHAR(5) NOT NULL, NOM VARCHAR(30) NOT NULL, PRENOM VARCHAR(20) NOT NULL, VERSION INTEGER NOT NULL, VILLE VARCHAR(30) NOT NULL, INDEMNITE_ID BIGINT NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.318--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--DROP TABLE INDEMNITES
[EL Fine]: 2012-06-22 14:35:03.338--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE INDEMNITES (ID BIGINT NOT NULL, BASE_HEURE DOUBLE NOT NULL, ENTRETIEN_JOUR DOUBLE NOT NULL, INDEMNITES_CP DOUBLE NOT NULL, INDICE INTEGER NOT NULL UNIQUE, REPAS_JOUR DOUBLE NOT NULL, VERSION INTEGER NOT NULL, PRIMARY KEY (ID))
[EL Fine]: 2012-06-22 14:35:03.418--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--ALTER TABLE EMPLOYES ADD CONSTRAINT FK_EMPLOYES_INDEMNITE_ID FOREIGN KEY (INDEMNITE_ID) REFERENCES INDEMNITES (ID)
[EL Fine]: 2012-06-22 14:35:03.568--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
[EL Fine]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Thread(Thread[main,5,main])--SELECT 1
[EL Warning]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Thread(Thread[main,5,main])--Ausnahme [EclipseLink-4002] (Eclipse Persistence Services – 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'sequence' already exists
Error Code: 1050
Call: CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))
Query: DataModifyQuery(sql="CREATE TABLE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT DECIMAL(38), PRIMARY KEY (SEQ_NAME))")
[EL Fine]: 2012-06-22 14:35:03.578--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--DELETE FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: 2012-06-22 14:35:03.638--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--SELECT * FROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: 2012-06-22 14:35:03.638--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) Werte (SEQ_GEN, 0)
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Verbindung(1296716340)--Thread(Thread[main,5,main])--Trennung
[EL Info]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Thread(Thread[main,5,main])--file:/D:/data/istia-1112/netbeans/glassfish/mv-pam/05/mv-pam-jpa-eclipselink/target/classes/_pam-jpa-eclipselinkPU Abmeldung erfolgreich
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Verbindung(1543921451)--Thread(Thread[main,5,main])--Trennung
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time: 3.503s
Finished at: Fri Jun 22 14:35:03 CEST 2012
Final Memory: 8M/153M
  • 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

  1. ein Projekt [mv-pam-jpa-hibernate-oracle] erstellen und testen, das eine Hibernate-Implementierung JPA und eine Oracle-Implementierung SGBD verwendet,
  2. ein Projekt [mv-pam-jpa-hibernate-mssql] erstellen und testen, das eine Hibernate-Implementierung JPA und einen SGBD-Server verwendet,
  3. Erstellen und Testen eines [mv-pam-jpa-eclipselink-oracle]-Projekts unter Verwendung einer JPA-Implementierung, EclipseLink und eines SGBD-Oracle-Servers,
  4. 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:

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
jpa.Employe[id=31,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
  • 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:

jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
jpa.Employe[id=453,version=1,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]

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:

dos>java -jar pam-spring-ui-metier-dao-jpa-eclipselink.jar 254104940426058 150 20

Valeurs saisies :
N° de sécurité sociale de l'employé : 254104940426058
Nombre d'heures travaillées : 150
Nombre de jours travaillés : 20

Informations Employé :
Nom : Jouveinal
...

Informations Cotisations :
CSGRDS : 3.49 %
...

Informations Indemnités :
...

Informations Salaire :
Salaire de base : 362.25 euro
Cotisations sociales : 97.48 euro
Indemnités d'entretien : 42.0 euro
Indemnités de repas : 62.0 euro
Salaire net : 368.77 euro

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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:

1
2
3
4
5
6
package metier;

public interface IMetier {
   // Gehaltsabrechnung abrufen
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
}
  • 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:
package metier;

import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;

public class FeuilleSalaire {
   // private Felder
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

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

package metier;

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

public interface IMetier {
   // Lohnabrechnung abrufen
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // Mitarbeiterliste
  public List<Employe> findAllEmployes();
}
  • 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:

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
       // neuen Beitrag anlegen
  public Cotisation create(Cotisation cotisation);
       // einen bestehenden Beitrag bearbeiten
  public Cotisation edit(Cotisation cotisation);
       // einen bestehenden Beitrag löschen
  public void destroy(Cotisation cotisation);
       // einen bestimmten Beitrag suchen
  public Cotisation find(Long id);
       // Alle Beitrag-Objekte abrufen
  public List<Cotisation> findAll();

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

       // einen neuen Beitrag anlegen
Cotisation create(Cotisation cotisation);

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:

       // einen neuen Beitrag anlegen
void create(Cotisation cotisation);

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:
       // einen neuen Beitrag anlegen
Cotisation create(Cotisation cotisation);
  • 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]:

       // einen neuen Beitrag anlegen
Cotisation create(Cotisation cotisation);

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

package dao;

import java.util.List;
import jpa.Cotisation;

public interface ICotisationDao {
       // neuen Beitrag anlegen
  public Cotisation create(Cotisation cotisation);
       // einen bestehenden Beitrag bearbeiten
  public Cotisation edit(Cotisation cotisation);
       // einen bestehenden Beitrag löschen
  public void destroy(Cotisation cotisation);
       // einen bestimmten Beitrag suchen
  public Cotisation find(Long id);
       // Alle Beitrag-Objekte abrufen
  public List<Cotisation> findAll();

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

package dao;

import java.util.List;
import jpa.Indemnite;

public interface IIndemniteDao {
     // eine Entschädigungseinheit anlegen
  public Indemnite create(Indemnite indemnite);
     // eine „Indemnite“-Entität bearbeiten
  public Indemnite edit(Indemnite indemnite);
     // eine „Indemnite“-Entität löschen
  public void destroy(Indemnite indemnite);
     // Entität „Indemnite“ anhand ihrer Kennung suchen
  public Indemnite find(Long id);
     // Alle „Indemnite“-Entitäten abrufen
  public List<Indemnite> findAll();

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

package dao;

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

public interface IEmployeDao {
     // eine neue „Employe“-Entität anlegen
  public Employe create(Employe employe);
     // Eine bestehende „Employe“-Entität bearbeiten
  public Employe edit(Employe employe);
     // Ein „Employe“-Objekt löschen
  public void destroy(Employe employe);
     // eine „Mitarbeiter“-Entität anhand ihrer ID suchen
  public Employe find(Long id);
     // eine „Employe“-Entität anhand ihrer Nummer suchen SS
  public Employe find(String SS);
     // Alle „Mitarbeiter“-Entitäten abrufen
  public List<Employe> findAll();
}
  • 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:

package exception;

@SuppressWarnings("serial")
public class PamException extends RuntimeException {

   // Fehlercode
  private int code;

  public PamException(int code) {
    super();
    this.code = code;
  }

  public PamException(String message, int code) {
    super(message);
    this.code = code;
  }

  public PamException(Throwable cause, int code) {
    super(cause);
    this.code = code;
  }

  public PamException(String message, Throwable cause, int code) {
    super(message, cause);
    this.code = code;
  }

   // Getter und Setter

  public int getCode() {
    return code;
  }

  public void setCode(int code) {
    this.code = code;
  }

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

package dao;

...

public class JUnitInitDB {

  private IEmployeDao employeDao = null;
  private ICotisationDao cotisationDao = null;
  private IIndemniteDao indemniteDao = null;

  @BeforeClass
  public void init(){
     // Anwendungskonfiguration
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
     // Schichten DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
  }

  @Test
  public void initDB(){
     // Die Datenbank wird gefüllt
...
     // Anzeigen des Datenbankinhalts
...
  }

  @Before()
  public void clean(){
     // Datenbank leeren
...
  }
}
  • 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:

------------- Standard Output ---------------
Employés ----------------------
jpa.Employe[id=5,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2]
jpa.Employe[id=6,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La brûlerie,ville=St Marcel,code postal=49014,indice=1]
Indemnités ----------------------
jpa.Indemnite[id=5,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0]
jpa.Indemnite[id=6,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités CP=15.0]
Cotisations ----------------------
jpa.Cotisation[id=3,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88]
------------- ---------------- ---------------

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:

package dao;

import exception.PamException;
...

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");
     // Schichten DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
  }

  @AfterClass
  public static void terminate() {
  }

  @Before()
  public void clean() {
...
  }

   // Protokolle
  private static void log(String message) {
    System.out.println("----------- " + message);
  }

   // Tests
  @Test
  public void test01() {
    log("test01");
     // Beitragsübersicht
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // Beitrag hinzufügen
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // auf Anfrage
    cotisation = cotisationDao.find(cotisation.getId());
     // Überprüfung
    Assert.assertNotNull(cotisation);
    Assert.assertEquals(3.49, cotisation.getCsgrds(), 1e-6);
    Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
    Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
    Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
     // wird geändert
    cotisation.setCsgrds(-1);
    cotisation.setCsgd(-1);
    cotisation.setRetraite(-1);
    cotisation.setSecu(-1);
    Cotisation cotisation2 = cotisationDao.edit(cotisation);
     // Überprüfungen
    Assert.assertEquals(cotisation.getVersion() + 1, cotisation2.getVersion());
    Assert.assertEquals(-1, cotisation2.getCsgrds(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getCsgd(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getRetraite(), 1e-6);
    Assert.assertEquals(-1, cotisation2.getSecu(), 1e-6);
     // das geänderte Element wird angefordert
    Cotisation cotisation3 = cotisationDao.find(cotisation2.getId());
     // Überprüfungen
    Assert.assertEquals(cotisation3.getVersion(), cotisation2.getVersion());
    Assert.assertEquals(-1, cotisation3.getCsgrds(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getCsgd(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getRetraite(), 1e-6);
    Assert.assertEquals(-1, cotisation3.getSecu(), 1e-6);
     // das Element wird gelöscht
    cotisationDao.destroy(cotisation3);
     // Prüfungen
    Cotisation cotisation4 = cotisationDao.find(cotisation3.getId());
    Assert.assertNull(cotisation4);
    cotisations = cotisationDao.findAll();
    Assert.assertEquals(nbCotisations, cotisations.size());
  }


  @Test
  public void test02(){
    log("test02");
     // Liste der Entschädigungen anfordern
...
     // Es wird eine Zulage hinzugefügt
..
     // Entschädigung aus der Datenbank abrufen – Entschädigung1 abrufen
..
     // Es wird überprüft, ob Zulage1 = Zulage ist
...
     // Die abgerufene Zulage wird geändert und die Änderung in BD gespeichert. Man erhält Zulage2
 ...
     // Die Version von Entschädigung2 wird überprüft
    ...
     // indemnite2 wird aus der Datenbank abgerufen – man erhält indemnite3
    ...
     // Es wird überprüft, ob indemnite3 = indemnite2 ist
    ...
     // Das Bild von indemnite3 wird aus der Datenbank gelöscht
    ...
     // indemnite3 wird aus der Datenbank abgerufen
    ...
     // Es wird überprüft, ob eine Nullreferenz erhalten wurde
 ...
  }

  @Test
  public void test03(){
    log("test03");
     // Es wird ein ähnlicher Test wie die vorherigen für „Employe“ wiederholt
 ...
  }

  @Test
  public void test04(){
    log("test04");
     // Die Methode [IEmployeDao].find(String SS) wird getestet
     // zunächst mit einem vorhandenen Mitarbeiter
     // dann mit einem nicht existierenden Mitarbeiter
...
  }

  @Test
  public void test05(){
    log("test05");
     // Es werden zwei Zulagen mit demselben Index angelegt
     // verstößt gegen die Eindeutigkeitsbedingung des Index
     // Es wird überprüft, ob eine Ausnahme vom Typ PamException auftritt
     // und dass sie die erwartete Fehlernummer aufweist
...
  }

  @Test
  public void test06(){
    log("test06");
     // Es werden zwei Mitarbeiter mit derselben Nummer SS angelegt
     // was gegen die Eindeutigkeitsbeschränkung für die Nummer SS verstößt
     // Es wird überprüft, ob eine Ausnahme vom Typ PamException auftritt
     // und dass sie die erwartete Fehlernummer aufweist
...

  }

  @Test
  public void test07(){
    log("test07");
     // Es werden zwei Mitarbeiter mit derselben Nummer SS angelegt, der erste mit „create“, der zweite mit „edit“
     // verstößt gegen die Eindeutigkeitsbeschränkung für die Nummer SS
     // Es wird überprüft, ob eine Ausnahme vom Typ PamException auftritt
     // und dass sie die erwartete Fehlernummer aufweist
...
  }

  @Test
  public void test08(){
    log("test08");
     // Das Löschen eines nicht existierenden Mitarbeiters löst keine Ausnahme aus
     // Er wird hinzugefügt und anschließend gelöscht – dies wird überprüft
...
  }

  @Test
  public void test09(){
    log("test09");
     // Das Bearbeiten eines Mitarbeiters ohne die richtige Version muss eine Ausnahme auslösen
     // Dies wird überprüft
...
  }

  @Test
  public void test10(){
    log("test10");
     // Das Löschen eines Mitarbeiters ohne die richtige Version muss eine Ausnahme auslösen
     // Das wird überprüft
...

  }

   // Getter und Setter
  ...
}

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:

  @Test
  public void test01() {
    log("test01");
     // Liste der Beiträge
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // Ein Beitrag wird hinzugefügt
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // Abfrage
    cotisation = cotisationDao.find(cotisation.getId());
     // Überprüfung
    Assert.assertNotNull(cotisation);
    Assert.assertEquals(0, cotisation.getCsgrds(), 1e-6);
    Assert.assertEquals(6.15, cotisation.getCsgd(), 1e-6);
    Assert.assertEquals(9.39, cotisation.getSecu(), 1e-6);
    Assert.assertEquals(7.88, cotisation.getRetraite(), 1e-6);
     // Änderung
....
}

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:

package metier;

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

public interface IMetier {
   // Lohnabrechnung abrufen
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // Mitarbeiterliste
  public List<Employe> findAllEmployes();
}

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:

package metier;

import jpa.Cotisation;
import jpa.Employe;
import jpa.Indemnite;

public class FeuilleSalaire implements Serializable{
   // private Felder
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

   // Konstruktoren
  public FeuilleSalaire() {

  }

  public FeuilleSalaire(Employe employe, Cotisation cotisation, ElementsSalaire elementsSalaire) {
    setEmploye(employe);
    setCotisation(cotisation);
    setElementsSalaire(elementsSalaire);
  }

   // toString
  public String toString() {
    return "[" + employe + "," + cotisation + "," + elementsSalaire + "]";
  }

   // Zugriffsrechte
...  
}
  • 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:

package metier;

public class ElementsSalaire implements Serializable{

   // private Felder
  private double salaireBase;
  private double cotisationsSociales;
  private double indemnitesEntretien;
  private double indemnitesRepas;
  private double salaireNet;

   // Hersteller
  public ElementsSalaire() {

  }

  public ElementsSalaire(double salaireBase, double cotisationsSociales,
    double indemnitesEntretien, double indemnitesRepas,
    double salaireNet) {
    setSalaireBase(salaireBase);
    setCotisationsSociales(cotisationsSociales);
    setIndemnitesEntretien(indemnitesEntretien);
    setIndemnitesRepas(indemnitesRepas);
  }

   // toString
  public String toString() {
    return "[salaire base=" + salaireBase + ",cotisations sociales=" + cotisationsSociales + ",indemnités d'entretien="
      + indemnitesEntretien + ",indemnités de repas=" + indemnitesRepas + ",salaire net="
      + salaireNet + "]";
  }

   // öffentliche Accessoren
...  
}
  • 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:

package metier;

...

@Transactional
public class Metier implements IMetier {

   // Verweis auf die Ebene [DAO]
  private ICotisationDao cotisationDao = null;
  private IEmployeDao employeDao=null;


   // Lohnabrechnung abrufen
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
...
  }

   // Mitarbeiterliste
   public List<Employe> findAllEmployes() {
     ...
  }

   // Getter und Setter
...
 }
  • 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:

package metier;

...

public class JUnitMetier_1 {

// Geschäftslogik-Schicht
  private IMetier metier;

  @BeforeClass
  public void init(){
     // Protokoll
    log("init");
     // Anwendungskonfiguration
     // Instanziierung der Schicht [metier]
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
    metier = (IMetier) ctx.getBean("metier");
     // Schichten DAO
    IEmployeDao employeDao=(IEmployeDao) ctx.getBean("employeDao");
    ICotisationDao cotisationDao=(ICotisationDao) ctx.getBean("cotisationDao");
    IIndemniteDao indemniteDao=(IIndemniteDao) ctx.getBean("indemniteDao");
     // Die Datenbank wird geleert
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // die Datenbank wird gefüllt
    Indemnite indemnite1=indemniteDao.create(new Indemnite(1,1.93,2,3,12));
    Indemnite indemnite2=indemniteDao.create(new Indemnite(2,2.1,2.1,3.1,15));
    Employe employe2=employeDao.create(new Employe("254104940426058","Jouveinal","Marie","5 rue des oiseaux","St Corentin","49203",indemnite2));
    Employe employe1=employeDao.create(new Employe("260124402111742","Laverti","Justine","La brûlerie","St Marcel","49014",indemnite1));
    Cotisation cotisation1=cotisationDao.create(new Cotisation(3.49,6.15,9.39,7.88));
  }

   // Protokolle
  private void log(String message) {
    System.out.println("----------- " + message);
  }

   // Test
  @Test
  public void test01(){
     // Lohnabrechnung
    System.out.println(metier.calculerFeuilleSalaire("260124402111742",30, 5));
    System.out.println(metier.calculerFeuilleSalaire("254104940426058",150, 20));
    try {
      System.out.println(metier.calculerFeuilleSalaire("xx", 150, 20));
    } catch (PamException ex) {
      System.err.println(String.format("PamException[Code=%d, message=%s]",ex.getCode(), ex.getMessage()));
    }
  }
}

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:

1
2
3
4
5
6
7
Testsuite: metier.JUnitMetier_1
----------- init
....
[jpa.Employe[id=22,version=0,SS=260124402111742,nom=Laverti,prenom=Justine,adresse=La brûlerie,ville=St Marcel,code postal=49014,indice=1],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88],jpa.Indemnite[id=29,version=0,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0],[salaire base=64.85,cotisations sociales=17.45,indemnités d'entretien=10.0,indemnités de repas=15.0,salaire net=72.4]]
[jpa.Employe[id=21,version=0,SS=254104940426058,nom=Jouveinal,prenom=Marie,adresse=5 rue des oiseaux,ville=St Corentin,code postal=49203,indice=2],jpa.Cotisation[id=6,version=0,csgrds=3.49,csgd=6.15,secu=9.39,retraite=7.88],jpa.Indemnite[id=30,version=0,indice=2,base heure=2.1,entretien jour2.1,repas jour=3.1,indemnités CP=15.0],[salaire base=362.25,cotisations sociales=97.48,indemnités d'entretien=42.0,indemnités de repas=62.0,salaire net=368.77]]
PamException[Code=101, message=L'employé de n°[xx] est introuvable]
Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 3,234 sec
  • 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:

package metier;

...
public class JUnitMetier_2 {

// Geschäftslogik
  private IMetier metier;

  @BeforeClass
  public void init(){
...
  }

   // Protokolle
  private void log(String message) {
    System.out.println("----------- " + message);
  }

   // Test
  @Test
  public void test01(){
...
  }
}

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:

Image

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:

package ui.console;

import exception.PamException;
import metier.FeuilleSalaire;
import metier.IMetier;

import java.util.ArrayList;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  /**
   * @param args
   */
  public static void main(String[] args) {
     // lokale Daten
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
     // Anzahl der Parameter wird überprüft
...
     // Fehlerliste
    ArrayList erreurs = new ArrayList();
     // Der zweite Parameter muss eine reelle Zahl > 0 sein
...
     // Fehler?
    if (...) {
      erreurs.add("Le nombre d'heures travaillées [" + args[1]
        + "] est erroné");
    }
     // Der dritte Parameter muss eine ganze Zahl > 0 sein
...
     // Fehler?
    if (...) {
      erreurs.add("Le nombre de jours travaillés [" + args[2]
        + "] est erroné");
    }
     // Fehler?
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // Alles in Ordnung – die Gehaltsabrechnung kann angefordert werden
    FeuilleSalaire feuilleSalaire = null;
    try {
       // Instanzierung der Ebene [metier]
      ...
       // Lohnabrechnung
      ...
    } catch (PamException ex) {
      System.err.println("L'erreur suivante s'est produite : "+ ex.getMessage());
      return;
    } catch (Exception ex) {
      System.err.println("L'erreur suivante s'est produite : "+ ex.toString());
      return;
    }

     // Detailansicht
    String output = "Valeurs saisies :\n";
    output += ajouteInfo("N° de sécurité sociale de l'employé", args[0]);
    output += ajouteInfo("Nombre d'heures travaillées", args[1]);
    output += ajouteInfo("Nombre de jours travaillés", args[2]);
    output += ajouteInfo("\nInformations Employé", "");
    output += ajouteInfo("Nom", feuilleSalaire.getEmploye().getNom());
    output += ajouteInfo("Prénom", feuilleSalaire.getEmploye().getPrenom());
    output += ajouteInfo("Adresse", feuilleSalaire.getEmploye().getAdresse());
    output += ajouteInfo("Ville", feuilleSalaire.getEmploye().getVille());
    output += ajouteInfo("Code Postal", feuilleSalaire.getEmploye().getCodePostal());
    output += ajouteInfo("Indice", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndice());
    output += ajouteInfo("\nInformations Cotisations", "");
    output += ajouteInfo("CSGRDS", ""+ feuilleSalaire.getCotisation().getCsgrds() + " %");
    output += ajouteInfo("CSGD", ""+ feuilleSalaire.getCotisation().getCsgd() + " %");
    output += ajouteInfo("Retraite", ""+ feuilleSalaire.getCotisation().getRetraite() + " %");
    output += ajouteInfo("Sécurité sociale", ""+ feuilleSalaire.getCotisation().getSecu() + " %");
    output += ajouteInfo("\nInformations Indemnités", "");
    output += ajouteInfo("Salaire horaire", ""+ feuilleSalaire.getEmploye().getIndemnite().getBaseHeure() + " euro");
    output += ajouteInfo("Entretien/jour", ""+ feuilleSalaire.getEmploye().getIndemnite().getEntretienJour() + " euro");
    output += ajouteInfo("Repas/jour", ""+ feuilleSalaire.getEmploye().getIndemnite().getRepasJour() + " euro");
    output += ajouteInfo("Congés Payés", ""+ feuilleSalaire.getEmploye().getIndemnite().getIndemnitesCP()+ " %");
    output += ajouteInfo("\nInformations Salaire", "");
    output += ajouteInfo("Salaire de base", ""+ feuilleSalaire.getElementsSalaire().getSalaireBase()+ " euro");
    output += ajouteInfo("Cotisations sociales", ""+ feuilleSalaire.getElementsSalaire().getCotisationsSociales()+ " euro");
    output += ajouteInfo("Indemnités d'entretien", ""+ feuilleSalaire.getElementsSalaire().getIndemnitesEntretien()+ " euro");
    output += ajouteInfo("Indemnités de repas", ""+ feuilleSalaire.getElementsSalaire().getIndemnitesRepas()+ " euro");
    output += ajouteInfo("Salaire net", ""+ feuilleSalaire.getElementsSalaire().getSalaireNet() + " euro");

    System.out.println(output);
  }

  static String ajouteInfo(String message, String valeur) {
    return message + " : " + valeur + "\n";
  }
}

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:

1
2
3
    private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {
       // TODO Fügen Sie hier Ihren Bearbeitungscode ein:
}

Der Java-Code, der die vorstehende Methode mit dem Klick auf die Schaltfläche „[JButtonSalaire]“ verknüpft, wird ebenfalls generiert:

1
2
3
4
5
6
    jButtonSalaire.setText("Salaire");
    jButtonSalaire.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButtonSalaireActionPerformed(evt);
      }
});

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:

  private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {                                         
 ...
  }

Der Java-Code, der die vorherige Methode dem Ereignis [caretUpdate] im Eingabefeld [jTextFieldHT] zuordnet, wird ebenfalls generiert:

1
2
3
4
5
    jTextFieldHT.addCaretListener(new javax.swing.event.CaretListener() {
      public void caretUpdate(javax.swing.event.CaretEvent evt) {
        jTextFieldHTCaretUpdate(evt);
      }
});

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:

1
2
3
    // Instanziierung der Schicht [metier]
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
IMetier metier = (IMetier) ctx.getBean("metier");

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:

package ui.swing;

...
public class PamJFrame extends javax.swing.JFrame {

   /** Erstellt ein neues Formular PamJFrame */
  public PamJFrame() {
    initComponents();
  }

   /** Diese Methode wird innerhalb des Konstruktors aufgerufen, um
   * initialize the form.
   * WARNING: Do NOT modify this code. The content of this method is
   * always regenerated by the Form Editor.
   */
   // <editor-fold defaultstate="collapsed" desc="Generierter Code">
  private void initComponents() {
...
  }// </editor-fold>

  private void jTextFieldHTCaretUpdate(javax.swing.event.CaretEvent evt) {                                         
 ...
  }                                        

  private void jButtonSalaireActionPerformed(java.awt.event.ActionEvent evt) {                                               
...
  }                                              

  public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new PamJFrame().setVisible(true);
      }
    });
  }

   // Variablendeklaration – nicht ändern
  private javax.swing.JButton jButtonSalaire;
...
   // Ende der Variablendeklaration

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

  /** Erstellt neues Formular PamJFrame */
  public PamJFrame() {
    initComponents();
    doMyInit();
  }

...

   // Instanzvariablen
  private IMetier metier=null;
  private List<Employe> employes=null;
  private String[] employesCombo=null;
  private double heuresTravaillées=0;

   // Benutzerspezifische Initialisierungen
  public void doMyInit(){
     // Kontextinitialisierung
    try{
       // Instanziierung der Schicht [metier]
...
     // Mitarbeiterliste
...
    }catch (PamException ex){
     // Die Ausnahmemeldung wird in [jTextAreaStatus] abgelegt
...
     // Zurück
      return;
    }
     // Schaltfläche „Gehalt“ deaktiviert
...
     // jScrollPane1 ausgeblendet
...
     // Spinner für gearbeitete Tage
    jSpinnerJT.setModel(new SpinnerNumberModel(0,0,31,1));
     // Kombinationsfeld „Mitarbeiter“
    employesCombo=new String[employes.size()];
    int i=0;
    for(Employe employe : employes){
      employesCombo[i++]=employe.getPrenom()+" "+employe.getNom();
    }
    jComboBoxEmployes.setModel(new DefaultComboBoxModel(employesCombo));
}
  • 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.

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:

  1. In [4] müssen die Spring-Konfigurationsdateien geändert werden. Dort ist nämlich die Konfiguration der Ebene JPA enthalten.
  2. 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:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

   <!-- Anwendungsschichten -->
   <!- - DAO -->
  <bean id="employeDao" class="dao.EmployeDao" />
  <bean id="indemniteDao" class="dao.IndemniteDao" />
  <bean id="cotisationDao" class="dao.CotisationDao" />
   <!-- Geschäftsbereich -->
  <bean id="metier" class="metier.Metier">
    <property name="employeDao" ref="employeDao"/>
    <property name="indemniteDao" ref="indemniteDao"/>
    <property name="cotisationDao" ref="cotisationDao"/>  
  </bean>

   <!-- Konfiguration JPA -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <!--
          <property name="showSql" value="true" />
    -->
        <property name="databasePlatform" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
        <property name="generateDdl" value="true" />
   <!--
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
    <property name="loadTimeWeaver">
      <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
  </bean>

   <!-- Datenquelle DBCP -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/dbpam_hibernate" />
    <property name="username" value="root" />
<!--
    <property name="password" value="" />
-->
  </bean>
....  
</beans>

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:

   <!-- Konfiguration JPA -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
        <!--
          <property name="showSql" value="true" />
  -->
        <property name="databasePlatform" value="org.eclipse.persistence.platform.database.MySQLPlatform" />
        <!--
        <property name="generateDdl" value="true" />
        -->
      </bean>
    </property>
    <property name="loadTimeWeaver">
      <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
    </property>
</bean>
  • 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):

1
2
3
4
5
6
7
8
9
<!-- Datenquelle DBCP -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/dbpam_eclipselink" />
    <property name="username" value="root" />
<!--
    <property name="password" value="" />
-->
  </bean>

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:

        <property name="generateDdl" value="true" />

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:

-javaagent:C:\...\spring-agent.jar

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

package dao;

...
@Transactional(propagation=Propagation.REQUIRED)
public class IndemniteDao implements IIndemniteDao{

  @PersistenceContext
  private EntityManager em;

   // Hersteller
  public IndemniteDao() {
  }

   // Entschädigung erstellen
  public Indemnite create(Indemnite indemnite) {
    try{
      em.persist(indemnite);
    }catch(Throwable th){
      throw new PamException(th,31);
    }
    return indemnite;
  }

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

----------- test05
4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
ATTENTION: SQL Error: 1062, SQLState: 23000
4 juin 2010 16:45:43 org.hibernate.util.JDBCExceptionReporter logExceptions
GRAVE: Duplicate entry '1' for key 2
Chaîne des exceptions --------------------------------------
exception.PamException
javax.persistence.EntityExistsException
org.hibernate.exception.ConstraintViolationException
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

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:

----------- test05
[EL Warning]: 2010-06-04 16:48:26.421--UnitOfWork(749304)--Ausnahme [EclipseLink-4002] (Eclipse Persistence Services – 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 2
Error Code: 1062
Call: INSERT INTO INDEMNITES (ID, ENTRETIEN_JOUR, REPAS_JOUR, INDICE, INDEMNITES_CP, BASE_HEURE, VERSION) VALUES (?, ?, ?, ?, ?, ?, ?)
        bind => [108, 2.0, 3.0, 1, 12.0, 1.93, 1]
Query: InsertObjectQuery(jpa.Indemnite[id=108,version=1,indice=1,base heure=1.93,entretien jour2.0,repas jour=3.0,indemnités CP=12.0])
Chaîne des exceptions --------------------------------------
org.springframework.transaction.TransactionSystemException
javax.persistence.RollbackException
org.eclipse.persistence.exceptions.DatabaseException
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException

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.