Skip to content

5. Version 1: Spring-/JPA-Architektur

Wir schlagen vor, sowohl eine Konsolenanwendung als auch eine grafische Anwendung zu schreiben, um Gehaltsabrechnungen für Kinderbetreuer zu erstellen, die bei der „Maison de la petite enfance“ in einer Gemeinde beschäftigt sind. Diese Anwendung wird die folgende Architektur aufweisen:

5.1. DB Die Datenbank

Die zur Erstellung der Gehaltsabrechnung benötigten statischen Daten werden in einer Datenbank gespeichert, die wir als “ (dbpam) bezeichnen. Diese Datenbank könnte die folgenden Tabellen enthalten:

Tabelle EMPLOYEES: enthält Informationen zu den verschiedenen Kinderbetreuungsanbietern

Struktur:

ID
Primärschlüssel
VERSION
Versionsnummer – wird bei jeder Änderung der Zeile erhöht
SS
Sozialversicherungsnummer des Mitarbeiters – eindeutig
NAME
Nachname des Mitarbeiters
Vorname
Vorname
ADRESSE
ihre Adresse
STADT
seine/ihre Stadt
POSTLEITZAHL
ihre Postleitzahl
INDEMNITE_ID
Fremdschlüssel auf das Feld [ID] der Tabelle [INDEMNITES]

Der Inhalt könnte wie folgt aussehen:

Image

Tabelle COTISATIONS: enthält die Prozentsätze, die zur Berechnung der Sozialversicherungsbeiträge benötigt werden

Struktur:

ID
Primärschlüssel
VERSION
Versionsnummer – wird bei jeder Änderung der Zeile erhöht
CSGRDS
Prozentsatz: Allgemeiner Sozialbeitrag + Beitrag zur Tilgung der Sozialschulden
CSGD
Prozentsatz: abzugsfähiger allgemeiner Sozialbeitrag
SECU
Prozentsatz: Sozialversicherung, Witwenrente, Altersrente
RENTE
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 „ALLOWANCES“: enthält die Elemente, die zur Berechnung des zu zahlenden Gehalts verwendet werden.
ID
Primärschlüssel
  
VERSION
Versionsnummer – wird bei jeder Änderung der Zeile erhöht
  
INDEX
Verarbeitungsindex – eindeutig
  
STUNDENSATZ
Nettopreis in Euro für eine Stunde Bereitschaftsdienst
  
TÄGLICHE WARTUNG
Tagespauschale in Euro pro Pflegetag
  
MAHLZEIT PRO TAG
Verpflegungszuschuss in Euro pro Pflegetag
  
Urlaubsgeld
Bezahlter Urlaubsanspruch. Dies ist ein Prozentsatz, der auf das Grundgehalt angewendet wird.
  
   

Der Inhalt könnte wie folgt lauten:

Image

Beachten Sie, dass die Zulagen von Kinderbetreuungsanbieter zu Kinderbetreuungsanbieter variieren können. Sie sind über die jeweilige Gehaltsstufe an einen bestimmten Kinderbetreuungsanbieter gebunden. So erhält Frau Marie Jouveinal, die der Gehaltsstufe 2 angehört (Tabelle „EMPLOYEES“), einen Stundenlohn von 2,1 Euro (Tabelle „INDEMNITES“).

5.2. Methode zur Berechnung des Gehalts einer Tagesmutter

Wir stellen nun die Methode zur Berechnung des Monatsgehalts einer Tagesmutter vor. Diese Methode ist nicht als die in der Praxis tatsächlich verwendete Methode gedacht. Als Beispiel verwenden wir das Gehalt von Frau Marie Jouveinal, die während des Abrechnungszeitraums 150 Stunden an 20 Tagen gearbeitet hat.

Folgende Faktoren werden berücksichtigt:

[TOTALHOURS]: Gesamtstundenzahl
, die im Laufe des Monats geleistet wurden

[TOTALDAYS]: Gesamtzahl der
im Monat
[TOTALHOURS]=150
[TOTALDAYS]= 20
Das Grundgehalt der Kinderbetreuungskraft
ergibt sich aus der folgenden Formel:
[BASISGEHALT]=([GESAMTSTUNDEN]
*[STUNDENSATZ])*(1+
[CPALLOWANCE]/100)
[BASESALARY]=
(150*[2,1])*(1+0,15)= 362,25
Ein bestimmter Betrag an Sozialversicherungsbeiträgen
müssen von diesem Grundgehalt abgezogen werden
abgezogen werden:

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

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

Sozialversicherung, Witwen-/Witwerrente und Altersrente:
 [BASESALARY]*[SECU/100]

Zusatzrente + AGPF +
Arbeitslosenversicherung:
 [GRUNDGEHALT]*[RENTE/100]
CSGRDS: 12,64
CSGD: 22,28
Sozialversicherung: 34,02
Rente: 28,55
Gesamtsumme der Sozialversicherungsbeiträge:
[SOCIALCONTRIBUTIONS]=
[BASISGEHALT]*(CSGRDS+CSGD
+SECU+PENSION)/100
[SOCIALCONTRIBUTIONS]=97,48
Darüber hinaus hat die Kinderbetreuerin Anspruch auf eine Tagegeldzulage und eine Verpflegungszulage für jeden Arbeitstag. Somit erhält sie folgende Zulagen:
[Zulagen] = [GESAMTZAHL DER TAGE]
*(TAGESZULAGE + TÄGLICHE VERPFLEGUNGSZULAGE)
[ZULAGEN]=104
Letztendlich ergibt sich folgender Nettolohn, der an die Kinderbetreuungskraft zu zahlen ist:
[GRUNDGEHALT] – [SOZIALVERSICHERUNGSBEITRÄGE] + [ZULAGEN]
[NETTOGEHALT]=368,77

5.3. So funktioniert die 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 erhält:

  1. die Sozialversicherungsnummer der Tagesmutter (im Beispiel 254104940426058 – Zeile 1)
  2. Gesamtzahl der geleisteten Arbeitsstunden (im Beispiel 150 – Zeile 1)
  3. Gesamtzahl der gearbeiteten Tage (im Beispiel 20 – Zeile 1)

Wir sehen, dass:

  • Zeilen 9–14: Informationen über den Arbeitnehmer anzeigen, dessen Sozialversicherungsnummer angegeben wurde
  • Zeilen 17–20: zeigen die Sätze für die verschiedenen Beiträge an
  • Zeilen 23–26: zeigen die Zulagen an, die mit der Gehaltsstufe des Mitarbeiters verbunden sind (hier Stufe 2)
  • Zeilen 29–33: Anzeige der Bestandteile des zu zahlenden Gehalts

Die Anwendung meldet etwaige 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 falschen 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 einer falschen 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. So funktioniert die grafische Anwendung

Die grafische Anwendung berechnet die Gehälter von Kinderbetreuern mithilfe eines Swing-Formulars:

  • Die als Parameter an das Konsolenprogramm übergebenen Informationen 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 Sozialversicherungsnummern der Mitarbeiter an, sondern deren Vor- und Nachnamen. Wir gehen hier davon aus, dass keine zwei Mitarbeiter denselben Vor- und Nachnamen haben.

5.5. Erstellen der Datenbank

Wir starten WampServer und verwenden das Tool PhpMyAdmin [1]:

  • Wählen Sie in [2] die Option [Datenbanken] aus,
  • Erstellen Sie in [3] eine Datenbank [dbpam_hibernate],
  • in [4] wird die Datenbank erstellt. Wählen Sie sie aus,
  • in [5] möchten wir ein SQL-Skript importieren,
  • in [6] wählen Sie die Datei über die Schaltfläche [Durchsuchen] aus,
  • Wählen Sie in [7,8] das SQL-Skript aus,
  • in [9] führen wir es aus,
  • In [10] wurden die Tabellen erstellt. Ihr Inhalt lautet wie folgt:

Tabelle „EMPLOYEES“

Image

Tabelle INDEMNITIES

Image

Tabelle BEITRÄGE

Image

5.6. JPA-Implementierung

5.6.1. JPA-/Hibernate-Schicht

Wir werden die JPA-Schicht in der folgenden Umgebung konfigurieren:

Ein Konsolenprogramm wird mit der Datenbank arbeiten. Dazu benötigen Sie:

  • eine Datenbank,
  • den JDBC-Treiber für das DBMS, in diesem Fall MySQL,
  • die JPA-Schicht mit Hibernate implementieren,
  • das Konsolenprogramm schreiben.

Wir erstellen das Maven-Projekt [mv-pam-jpa-hibernate] [1]:

In unserer Anwendungsarchitektur benötigen wir die folgenden Elemente:

  • die Datenbank,
  • den JDBC-Treiber für das MySQL-DBMS,
  • die JPA/Hibernate-Schicht (Entitäten und Konfiguration),
  • das Testkonsolenprogramm.

5.6.1.1. Die Datenbank

Zunächst erstellen wir eine leere Datenbank. Wir starten WampServer und verwenden das Tool PhpMyAdmin [1]:

  • Wählen Sie in [2] die Option [Datenbanken] aus,
  • Erstellen Sie in [3] eine Datenbank mit dem Namen [dbpam_hibernate],
  • in [4] wird die Datenbank erstellt.

5.6.1.2. Konfiguration der JPA-Schicht

Die Verbindung zwischen der JDBC-Schicht und der Datenbank wird in der Datei [persistence.xml] konfiguriert, die die JPA-Schicht konfiguriert. Diese Datei kann mit NetBeans erstellt werden:

  • Stellen Sie auf der Registerkarte [Services] [1] über den MySQL-JDBC-Treiber [2] eine Verbindung zur Datenbank her
  • geben Sie unter [3] den Namen der Datenbank ein, zu der Sie eine Verbindung herstellen möchten.
  • in [4] die JDBC-URL der Datenbank,
  • unter [5] melden Sie sich als root ohne Passwort an,
  • in [6] können Sie die Verbindung testen,
  • in [7] war die Verbindung erfolgreich.
  • Die Verbindung erscheint in [8] und [9],
  • füge in [10] ein neues Element zum Projekt hinzu,
  • wählen Sie in [11] die Kategorie [Persistence] und in [12] das Element [Persistence Unit] aus,
  • in [13] benennen Sie diese Persistence Unit,
  • in [14] wählen wir eine Hibernate-Implementierung aus,
  • Geben Sie in [15] die soeben erstellte Verbindung zur MySQL-Datenbank an,
  • Geben Sie in [16] an, dass bei der Instanziierung der JPA-Schicht die Tabellen erstellt werden müssen, die den JPA-Entitäten des Projekts entsprechen.

Am Ende des Assistenten wird die Datei [persistence.xml] generiert:

  • Die Datei erscheint in einem neuen Zweig des Projekts, im Ordner [META-INF] [1],
  • der dem Ordner [src/main/resources] des Projekts entspricht [2,3].

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 Persistence-Unit und der Transaktionstyp. RESOURCE_LOCAL gibt an, dass das Projekt Transaktionen selbst verwaltet. In diesem Fall übernimmt das Konsolenprogramm diese Aufgabe.
  • Zeile 4: Die verwendete JPA-Implementierung ist Hibernate,
  • Zeilen 6–9: die JDBC-Eigenschaften für die Datenbankverbindung,
  • Zeile 11: Fordert die Erstellung von Tabellen an, die den JPA-Entitäten entsprechen. Tatsächlich generiert NetBeans hier eine fehlerhafte Konfiguration. Die Konfiguration sollte wie folgt lauten:

      <property name="hibernate.hbm2ddl.auto" value="create"/>

Mit der Option create löscht Hibernate bei der Instanziierung der JPA-Schicht die den JPA-Entitäten entsprechenden Tabellen und erstellt sie anschließend neu. Die Option create-drop bewirkt dasselbe, löscht jedoch am Ende des Lebenszyklus der JPA-Schicht alle Tabellen. Es gibt noch eine weitere Option:


      <property name="hibernate.hbm2ddl.auto" value="update"/>

Diese Option erstellt die Tabellen, falls sie 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"/>

Diese Einstellungen weisen Hibernate an, die SQL-Anweisungen anzuzeigen, die es an die Datenbank sendet. Die vollständige Datei sieht daher 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. Abhängigkeiten

Kehren wir zur Projektarchitektur zurück:

Wir haben die JPA-Schicht über die Datei [persistence.xml] konfiguriert. Als Implementierung wurde Hibernate gewählt. Dadurch entstanden Abhängigkeiten im Projekt:

  

Diese Abhängigkeiten sind auf die Einbindung von Hibernate in das Projekt zurückzuführen. Wir müssen eine weitere Abhängigkeit hinzufügen: den MySQL-JDBC-Treiber, der die JDBC-Schicht der Architektur implementiert. Wir aktualisieren die Datei [pom.xml] wie folgt:


<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 für den MySQL-JDBC-Treiber hinzugefügt.

5.6.1.4. JPA-Entitäten


Frage: Erstellen Sie gemäß dem Ansatz im Beispiel in Abschnitt 4.4 die Entitäten [Cotisation, Indemnite, Employe].


Hinweise:

  • Die Entitäten werden Teil eines Pakets namens [jpa] sein,
  • jede Entität erhält eine Versionsnummer,
  • wenn zwei Entitäten durch eine Beziehung verknüpft sind, wird nur die primäre @ManyToOne-Beziehung erstellt. Die inverse @OneToMany-Beziehung wird nicht erstellt.

5.6.1.5. Der Code für die Hauptklasse

Wir fügen die zuvor entwickelten JPA-Entitäten [1] in das Projekt ein:

Anschließend fügen wir [2] die folgende [main.Main]-Klasse hinzu:


package main;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
 
public class Main {
 
  public static void main(String[] args) {
    // creating the Entity Manager is enough to build the JPA layer
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-pam-jpa-hibernatePU");
    EntityManager em=emf.createEntityManager();
    // resource release
    em.close();
    emf.close();
  }
}
  • Zeile 10: Wir erstellen die EntityManagerFactory für die 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: Der EntityManager wird erstellt. Dadurch wird die JPA-Schicht erstellt. Die Datei [persistence.xml] wird verwendet, und somit werden die Datenbanktabellen erstellt.
  • Zeilen 14–15: Ressourcen werden freigegeben.

5.6.1.6. Tests

Kehren wir zur Architektur unseres Projekts zurück:

Alle Schichten wurden implementiert. Wir führen das Projekt aus [2].

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

Die Konsole enthält nur Hibernate-Protokolle, da das ausgeführte Programm nichts anderes tut, als die JPA-Schicht zu instanziieren. Beachten Sie folgende Punkte:

  • Zeile 43: Hibernate versucht, den Fremdschlüssel aus der Tabelle [EMPLOYEES] zu löschen,
  • Zeilen 51–55: Löschen der drei Tabellen,
  • Zeile 57: Erstellung der Tabelle [COTISATIONS],
  • Zeile 67: Erstellung der Tabelle [EMPLOYEES],
  • Zeile 80: Erstellung der Tabelle [INDEMNITIES],
  • Zeile 91: Erstellung des Fremdschlüssels für die Tabelle [EMPLOYEES].

In NetBeans können Sie die Tabellen in der zuvor erstellten Verbindung anzeigen:

Die erstellten Tabellen hängen sowohl von der verwendeten JPA-Schicht-Implementierung als auch vom verwendeten DBMS ab. Daher kann eine JPA/EclipseLink-Implementierung mit derselben Datenbank unterschiedliche Tabellen generieren. Das werden wir uns nun ansehen.

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

Wir werden die Schritte aus dem vorherigen Abschnitt befolgen:

  1. Erstellen Sie eine MySQL-Datenbank [dbpam_eclipselink]. Wir verwenden das Skript [dbpam_eclipselink.sql], um diese zu generieren,
  2. die Projektdatei [persistence.xml] erstellen. Verwenden Sie die EclipseLink JPA 2.0-Implementierung,
  3. füge die Abhängigkeit vom MySQL-JDBC-Treiber zu den generierten Abhängigkeiten hinzu,
  4. füge die JPA-Entitäten und das Konsolenprogramm hinzu,
  5. führen Sie die Tests aus.

Die Datei [persistence.xml] sieht 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. Die Stufe „FINE“ ermöglicht es uns, die SQL-Anweisungen zu sehen, die EclipseLink in der Datenbank ausführen wird.
  • Zeile 15: Wenn die JPA/EclipseLink-Schicht instanziiert wird, werden die JPA-Entitätstabellen gelöscht und anschließend neu erstellt.

Die Konsolenausgabe lautet wie folgt:

------------------------------------------------------------------------
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])--The access type for the persistent class [class jpa.Cotisation] is set to [FIELD].
[EL Config]: 2012-06-22 14:35:01.884--ServerSession(730572764)--Thread(Thread[main,5,main])--The access type for the persistent class [class jpa.Employe] is set to [FIELD].
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--The target entity (reference) class for the many to one mapping element [field indemnite] is being defaulted to: class jpa.Indemnite.
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--The access type for the persistent class [class jpa.Indemnite] is set to [FIELD].
[EL Config]: 2012-06-22 14:35:01.899--ServerSession(730572764)--Thread(Thread[main,5,main])--The alias name for the entity class [class jpa.Cotisation] is being defaulted to: Cotisation.
[EL Config]: 2012-06-22 14:35:01.915--ServerSession(730572764)--Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--The alias name for the entity class [class jpa.Employe] is being defaulted to: Employe.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--The alias name for the entity class [class jpa.Indemnite] is being defaulted to: Indemnite.
[EL Config]: 2012-06-22 14:35:01.93--ServerSession(730572764)--Thread(Thread[main,5,main])--The column name for element [id] is being defaulted to: ID.
[EL Config]: 2012-06-22 14:35:01.962--ServerSession(730572764)--Thread(Thread[main,5,main])--The primary key column name for the mapping element [field indemnite] is being defaulted to: ID.
[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)--Connection(1543921451)--Thread(Thread[main,5,main])--connecting(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)--Connection(1296716340)--Thread(Thread[main,5,main])--Connected: 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 login successful
[EL Fine]: 2012-06-22 14:35:02.818--ServerSession(730572764)--Connection(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)--Connection(1296716340)--Thread(Thread[main,5,main])--DROP TABLE COTISATIONS
[EL Fine]: 2012-06-22 14:35:03.118--ServerSession(730572764)--Connection(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)--Connection(1296716340)--Thread(Thread[main,5,main])--DROP TABLE EMPLOYES
[EL Fine]: 2012-06-22 14:35:03.238--ServerSession(730572764)--Connection(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)--Connection(1296716340)--Thread(Thread[main,5,main])--DROP TABLE INDEMNITES
[EL Fine]: 2012-06-22 14:35:03.338--ServerSession(730572764)--Connection(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)--Connection(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)--Connection(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])--Exception [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)--Connection(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)--Connection(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)--Connection(1296716340)--Thread(Thread[main,5,main])--INSERT INTO SEQUENCE(SEQ_NAME, SEQ_COUNT) values (SEQ_GEN, 0)
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Connection(1296716340)--Thread(Thread[main,5,main])--disconnect
[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 logout successful
[EL Config]: 2012-06-22 14:35:03.748--ServerSession(730572764)--Connection(1543921451)--Thread(Thread[main,5,main])--disconnect
------------------------------------------------------------------------
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 MySQL-Datenbank,
  • Zeilen 31–34: Bestätigung, dass die Verbindung erfolgreich hergestellt wurde,
  • Zeile 36: Löschen des Fremdschlüssels aus der Tabelle [EMPLOYEES],
  • Zeile 37: Löschen der Tabelle [COTISATIONS],
  • Zeile 38: Erstellung der Tabelle [CONTRIBUTIONS]. Es ist anzumerken, dass der Primärschlüssel ID nicht über das MySQL-Attribut auto_increment verfügt. Das bedeutet, dass MySQL die Primärschlüsselwerte nicht generiert,
  • Zeile 39: Löschen der Tabelle [EMPLOYEES],
  • Zeile 40: Erstellen der Tabelle [EMPLOYEES]. Ihr Primärschlüssel ID verfügt nicht über das MySQL-Attribut auto_increment,
  • Zeile 41: Löschen der Tabelle [INDEMNITIES],
  • Zeile 42: Erstellung der Tabelle [INDEMNITIES]. Ihr Primärschlüssel ID verfügt nicht über das MySQL-Attribut auto_increment,
  • Zeile 43: Erstellen eines Fremdschlüssels von der Tabelle [EMPLOYEES] zur Tabelle [BENEFITS],
  • Zeile 44: Erstellen einer Tabelle [SEQUENCE]. Diese wird verwendet, um die Primärschlüssel für die drei vorherigen Tabellen zu generieren,
  • 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 [1] überprüft werden:

Daher generieren die JPA-Implementierungen von Hibernate und EclipseLink auf der Grundlage derselben JPA-Entitäten nicht dieselben Tabellen. Im weiteren Verlauf dieses Dokuments gilt für die verwendete JPA-Implementierung:

  • Hibernate, verwenden wir die Datenbank [dbpam_hibernate],
  • EclipseLink, verwenden wir die Datenbank [dbpam_eclipselink].

5.6.3. Zu erledigende Aufgaben

Nach dem gleichen Verfahren wie zuvor,

  1. Erstellen und testen Sie ein Projekt [mv-pam-jpa-hibernate-oracle] unter Verwendung einer Hibernate-JPA-Implementierung und eines Oracle-DBMS,
  2. Erstellen und testen Sie ein Projekt [mv-pam-jpa-hibernate-mssql] unter Verwendung einer Hibernate-JPA-Implementierung und eines SQL-Server-DBMS,
  3. Erstellen und Testen Sie ein Projekt [mv-pam-jpa-eclipselink-oracle] unter Verwendung einer EclipseLink-JPA-Implementierung und eines Oracle-DBMS,
  4. Erstellen und Testen eines Projekts [mv-pam-jpa-eclipselink-mssql] unter Verwendung einer EclipseLink-JPA-Implementierung und eines SQL Server-DBMS,

5.6.4. Lazy oder Eager?

Kehren wir zu einer möglichen Definition der Entität [Employee] zurück:


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;
  ...
}

Die Zeilen 27–29 definieren den Fremdschlüssel von der Tabelle [EMPLOYEES] zur Tabelle [INDEMNITIES]. Das Fetch-Attribut in Zeile 27 definiert die Abrufstrategie für das Feld „indemnity“ in Zeile 29. Es gibt zwei Modi:

  • FetchType.LAZY: Wenn ein Mitarbeiter abgefragt wird, wird die entsprechende Entschädigung nicht abgerufen. Sie wird abgerufen, wenn zum ersten Mal auf das Feld [Employee].indemnity verwiesen wird.
  • FetchType.EAGER: Bei der Suche nach einem Mitarbeiter wird die entsprechende Zulage abgerufen. Dies ist der Standardmodus, wenn kein Modus angegeben wird.

Um den Vorteil der Option FetchType.LAZY zu verstehen, betrachten Sie das folgende Beispiel. Auf einer Webseite wird eine Liste von Mitarbeitern ohne Angabe ihrer Vergütung mit einem [Details]-Link angezeigt. Durch Klicken auf diesen Link wird dann die Vergütung für den ausgewählten Mitarbeiter angezeigt. Wir sehen, dass:

  • Um die erste Seite anzuzeigen, benötigen wir die Mitarbeiter nicht zusammen mit ihren Sozialleistungen. Der Modus „FetchType.LAZY“ ist daher geeignet;
  • Um die zweite Seite mit den Details anzuzeigen, muss eine zusätzliche Abfrage an die Datenbank gesendet werden, um die Leistungen 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] wird das Projekt kopiert,
  • in [2] geben wir den Ordner für die Kopie an und in [3] dessen Namen,
  • in [4] hat das neue Projekt denselben Namen wie das alte. Das ändern wir:
  • In [1] benennen wir das Projekt um,
  • in [2] benennen wir das Projekt und seine artifactId um,
  • 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 {
 
  // the JPQL query below brings back an employee
  // the foreign key [Employe].indemnite is in FetchType.LAZY
  public static void main(String[] args) {
    // creating the Entity Manager is enough to build the JPA layer
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("pam-jpa-hibernatePU");
    // first attempt
    EntityManager em = emf.createEntityManager();
    Employe employe = (Employe) em.createQuery("select e from Employe e where e.nom=:nom").setParameter("nom", "Jouveinal").getSingleResult();
    em.close();
    // we display the employee
    try {
      System.out.println(employe);
    } catch (Exception ex) {
      System.out.println(ex);
    }
    // second test
    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();
    // free up resources
    em.close();
    // we display the employee
    try {
      System.out.println(employe);
    } catch (Exception ex) {
      System.out.println(ex);
    }
    // resource release
    emf.close();
  }
}
  • Zeile 15: Wir erstellen die EntityManagerFactory für die JPA-Schicht,
  • Zeile 17: Wir rufen den EntityManager ab, der uns die Interaktion mit der JPA-Schicht ermöglicht,
  • Zeile 18: Wir rufen den Mitarbeiter namens Jouveinal ab,
  • Zeile 19: Wir schließen den EntityManager. Dadurch wird der Persistenzkontext geschlossen.
  • Zeile 22: Wir zeigen den abgerufenen Mitarbeiter an.

Die Klasse [Employee] sieht wie folgt aus:


package jpa;
 
...
 
@Entity
@Table(name="EMPLOYES")
public class Employe implements Serializable {
 
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
  @Version
  @Column(name="VERSION",nullable=false)
  private int version;
  @Column(name="SS", nullable=false, unique=true, length=15)
  private String SS;
  @Column(name="NOM", nullable=false, length=30)
  private String nom;
  @Column(name="PRENOM", nullable=false, length=20)
  private String prenom;
  @Column(name="ADRESSE", nullable=false, length=50)
  private String adresse;
  @Column(name="VILLE", nullable=false, length=30)
  private String ville;
  @Column(name="CP", nullable=false, length=5)
  private String codePostal;
  @ManyToOne(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 im LAZY-Modus abgerufen,
  • Zeile 47: verwendet das Feld „indemnite“. Wenn die Methode „toString“ aufgerufen wird, während das Feld „indemnite“ noch nicht abgerufen wurde, wird es an dieser Stelle abgerufen. Es sei denn, der Persistenzkontext wurde geschlossen, wie im Beispiel.

Kehren wir zum [Main]-Code zurück:

  • Zeilen 21–25: Es sollte eine Ausnahme auftreten. Der Grund dafür ist, dass die toString-Methode aufgerufen wird. Diese verwendet das Feld „indemnite“. Dieses Feld wird nachgeschlagen. Da der Persistenzkontext geschlossen wurde, existiert die abgerufene [Employee]-Entität nicht mehr, daher die Ausnahme.
  • Zeile 27: Wir erstellen einen neuen EntityManager,
  • Zeile 28: Wir rufen den Mitarbeiter Jouveinal ab, indem wir die zugehörige Zulage in der JPQL-Abfrage explizit anfordern. Diese explizite Anforderung ist notwendig, da der Abrufmodus für diese Zulage LAZY ist,
  • Zeile 30: Wir schließen den EntityManager,
  • Zeilen 32–36: Wir zeigen den Mitarbeiter erneut an. Es sollte keine Ausnahme auftreten.

Um das Projekt auszuführen, benötigen Sie eine mit Daten gefüllte Datenbank. Diese erstellen Sie, indem Sie die Schritte in Abschnitt 5.5 befolgen. 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>
  • Wir haben die Option entfernt, die die Tabellen erstellt hat. Die Datenbank ist hier bereits vorhanden und gefüllt,
  • und wir haben die Optionen entfernt, die dazu führten, dass Hibernate die an die Datenbank gesendeten SQL-Anweisungen protokollierte.

Beim Ausführen des Projekts werden die folgenden beiden Meldungen in der Konsole ausgegeben:

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 beim Versuch auftrat, die fehlende Vergütung abzurufen, während die Sitzung geschlossen war. Wir sehen, dass die Vergütung aufgrund des LAZY-Modus nicht abgerufen wurde,
  • Zeile 2: Der Mitarbeiter mit seiner Zulage, abgerufen über eine Abfrage, die den LAZY-Modus umgangen hat.

5.6.5. Zu erledigende Aufgaben

Erstellen Sie nach einem ähnlichen Verfahren wie dem soeben beschriebenen ein Projekt [mv-pam-pa-eclipselink-lazy], das das Verhalten von EclipseLink im LAZY-Modus demonstriert.

Es werden folgende Ergebnisse erzielt:

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 LAZY-Modus lieferten beide Abfragen neben dem Mitarbeiter auch die Vergütung. Bei der Online-Recherche zu dieser Anomalie stellen wir fest, dass die Anmerkung [FetchType.LAZY] (Zeile 1):


  @ManyToOne(fetch= FetchType.LAZY)
  @JoinColumn(name="INDEMNITE_ID",nullable=false)
private Indemnite indemnite;

ist keine Anforderung, sondern ein Vorschlag. Der JPA-Implementierer ist nicht verpflichtet, sich daran zu halten. Wir sehen daher, dass der Code manchmal von der verwendeten JPA-Implementierung abhängig wird. Es ist möglich, EclipseLink so zu konfigurieren, dass es sich im LAZY-Modus wie erwartet verhält.

5.6.6. Weiter

Die Architektur der zu erstellenden Anwendung sieht wie folgt aus:

Im weiteren Verlauf dieses Dokuments werden wir das Maven-Projekt [mv-pam-jpa-hibernate] in das Projekt [mv-pam-spring-hibernate] kopieren [1, 2, 3]:

  • Anschließend benennen wir das neue Projekt um [4, 5, 6].

Wir werden die Abhängigkeiten des neuen Projekts ändern. Die Datei [pom.xml] sieht dann 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 JUnit-Tests,
  • Zeilen 32–41: Abhängigkeiten für den Apache DBCP-Verbindungspool,
  • Zeilen 42–65: Abhängigkeiten für das Spring-Framework,
  • Zeilen 67–71: Abhängigkeiten für die JPA/Hibernate-Implementierung,
  • Zeilen 72–76: die Abhängigkeit für den MySQL-JDBC-Treiber,
  • Zeilen 77–81: die Abhängigkeit für die Swing-Schnittstelle. Diese wird von NetBeans automatisch hinzugefügt, wenn eine Swing-Schnittstelle zum Projekt hinzugefügt wird.

Außerdem werden wir die beiden MySQL-Datenbanken generieren:

  • [dbpam_hibernate] aus dem Skript [dbpam_hibernate.sql],
  • [dbpam_eclipselink] aus dem Skript [dbpam_eclipselink.sql],

5.7. Die Schnittstellen-IM s für die [Business]- und [DAO]-Schichten

Kehren wir zur Anwendungsarchitektur zurück:

Welche Schnittstelle sollte in der oben dargestellten Architektur die [DAO]-Schicht gegenüber der [Business]-Schicht bereitstellen, und welche Schnittstelle sollte die [Business]-Schicht gegenüber der [UI]-Schicht bereitstellen? Ein erster Ansatz zur Definition der Schnittstellen der verschiedenen Schichten besteht darin, die verschiedenen Anwendungsfälle der Anwendung zu untersuchen. Hier haben wir zwei, je nach gewählter Benutzeroberfläche: Konsole oder grafisches Formular.

Betrachten wir, wie die Konsolenanwendung genutzt wird:

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 App erhält drei Angaben vom Nutzer (siehe Zeile 1 oben)

  • die Sozialversicherungsnummer der Kinderbetreuungskraft
  • die Anzahl der im Monat geleisteten Arbeitsstunden
  • die Anzahl der im Monat gearbeiteten Tage

Auf der Grundlage dieser Informationen und weiterer in Konfigurationsdateien gespeicherter Daten zeigt die Anwendung folgende Informationen an:

  • Zeilen 4–6: die eingegebenen Werte
  • Zeilen 8–10: Informationen zu dem Mitarbeiter, dessen Sozialversicherungsnummer angegeben wurde
  • Zeilen 12–14: die Sätze für die verschiedenen Sozialversicherungsbeiträge
  • Zeilen 16–17: die verschiedenen Zulagen, die an die Kinderbetreuungskraft gezahlt werden
  • Zeilen 19–24: Posten auf der Gehaltsabrechnung der Kinderbetreuungskraft

Bestimmte Informationen müssen von der [Business]-Ebene an die [UI]-Ebene übermittelt werden:

  1. Informationen zu einem Kinderbetreuungsanbieter, der anhand seiner Sozialversicherungsnummer identifiziert wird. Diese Informationen befinden sich in der Tabelle [EMPLOYEES]. Dadurch können die Zeilen 6–8 angezeigt werden.
  2. die Beträge der verschiedenen Sozialversicherungsbeiträge, die vom Bruttogehalt 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 befinden sich in der Tabelle [INDEMNITES]. Dadurch können die Zeilen 14–15 angezeigt werden.
  4. die Bestandteile des Gehalts, die in den Zeilen 18–22 angezeigt werden.

Auf dieser Grundlage könnten wir uns für eine erste Implementierung der Schnittstelle [IMetier] entscheiden, die von der [metier]-Schicht an die [ui]-Schicht übergeben wird:

1
2
3
4
5
6
package metier;

public interface IMetier {
   // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
}
  • Zeile 1: Die Elemente der [business]-Schicht werden im [business]-Paket abgelegt
  • Zeile 5: Die Methode [calculatePaystub] nimmt als Parameter die drei von der [ui]-Schicht erhaltenen Informationen entgegen und gibt ein Objekt vom Typ [Paystub] zurück, das die Informationen enthält, die die [ui]-Schicht auf der Konsole anzeigen wird. Die Klasse [ Paystub] könnte wie folgt aussehen:
package metier;

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

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

  ...
}
  • Zeile 9: der auf der Gehaltsabrechnung aufgeführte Mitarbeiter – Information Nr. 1, die von der [ui]-Ebene angezeigt wird
  • Zeile 10: die verschiedenen Beitragssätze – Information Nr. 2, die von der [ui]-Ebene angezeigt wird
  • Zeile 11: die verschiedenen Zulagen, die an den Index des Mitarbeiters gekoppelt sind – Information Nr. 3, angezeigt von der [ui]-Ebene
  • Zeile 12: die Bestandteile seines Gehalts – Information Nr. 4, angezeigt von der [ui]-Ebene

Ein zweiter Anwendungsfall für die [business]-Ebene ergibt sich im Zusammenhang mit der grafischen Benutzeroberfläche:

Wie oben gezeigt, werden in der Dropdown-Liste [1, 2] alle Mitarbeiter angezeigt. Diese Liste muss von der [Business]-Schicht angefordert werden. Die Schnittstellen ace für diese Schicht entwickelt sich dann wie folgt:

package metier;

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

public interface IMetier {
   // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // list of employees
  public List<Employe> findAllEmployes();
}
  • Zeile [10]: Die Methode, mit der die [UI]-Schicht die Liste aller Mitarbeiter von der [Business]-Schicht anfordern kann.

Die [Business]-Schicht kann die Felder [Employee, Contribution, Allowance] des oben genannten [Payroll]-Objekts nur durch Abfragen der [DAO]-Schicht initialisieren, da diese Informationen in den Datenbanktabellen gespeichert sind. Dasselbe gilt für das Abrufen der Liste aller Mitarbeiter. Wir könnten eine einzige [DAO]-Schnittstelle erstellen, um den Zugriff auf die drei Entitäten [Employee, Contribution, Allowance] zu verwalten. Wir haben uns hier jedoch dafür entschieden, pro Entität eine [DAO]-Schnittstelle zu erstellen.

Die [DAO]-Schnittstelle für den Zugriff auf die [Contribution]-Entitäten in der [CONTRIBUTIONS]-Tabelle sieht wie folgt aus:

package dao;

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

public interface ICotisationDao {
       // create a new contribution
  public Cotisation create(Cotisation cotisation);
       // modify an existing contribution
  public Cotisation edit(Cotisation cotisation);
       // delete an existing contribution
  public void destroy(Cotisation cotisation);
       // search for a specific contribution
  public Cotisation find(Long id);
       // get all objects Contribution
  public List<Cotisation> findAll();

}
  • Zeile 6: Die Schnittstelle [ICotisationDao] verwaltet den Zugriff auf die Entität [Cotisation] und damit auf die Tabelle [COTISATIONS] in der Datenbank. Unsere Anwendung benötigt lediglich die Methode [findAll] in Zeile 16, die den gesamten Inhalt der Tabelle [COTISATIONS] abruft. Hier wollten wir einen allgemeineren Fall behandeln, in dem alle CRUD-Operationen (Create, Read, Update, Delete) an der Entität durchgeführt werden.
  • Zeile 8: Die Methode [create] erstellt eine neue [Cotisation]-Entität
  • Zeile 10: Die Methode [edit] ändert eine vorhandene [Cotisation]-Entität
  • Zeile 12: Die Methode [destroy] löscht eine vorhandene [Cotisation]-Entität
  • Zeile 14: Die Methode [find] ruft eine vorhandene [Cotisation]-Entität anhand ihrer ID ab
  • Zeile 16: Die Methode [findAll] gibt eine Liste aller vorhandenen [Membership]-Entitäten zurück

Schauen wir uns die Signatur der Methode [create] einmal genauer an:

      // créer une nouvelle cotisation
Cotisation create(Cotisation cotisation);

Die Methode create hat einen Parameter cotisation vom Typ Cotisation. Der Parameter cotisation muss persistent sein, d. h. in der Tabelle [COTISATIONS] gespeichert werden. Vor dieser Persistenz hat der Parameter cotisation einen Identifikator id ohne Wert. Nach der Persistenz hat das Feld id einen Wert, der dem Primärschlüssel des Datensatzes entspricht, der zur Tabelle [COTISATIONS] hinzugefügt wurde. Der Parameter cotisation ist daher ein Eingabe-/Ausgabeparameter der Methode create. Es erscheint nicht notwendig, dass die create-Methode den Parameter cotisation zusätzlich als Ergebnis zurückgibt. Da die aufrufende Methode eine Referenz auf das Objekt [Cotisation cotisation] hält, hat sie bei einer Änderung Zugriff auf das geänderte Objekt, da sie eine Referenz darauf besitzt. Sie kann daher den Wert kennen, den die create-Methode dem Feld id des Objekts [Cotisation cotisation] zugewiesen hat. Die Methodensignatur könnte daher wie folgt vereinfacht werden:

      // créer une nouvelle cotisation
void create(Cotisation cotisation);

Beim Schreiben einer Schnittstelle ist zu beachten, dass diese in zwei verschiedenen Kontexten verwendet werden kann: im lokalen und im Remote- . Im lokalen Kontext werden die aufrufende und die aufgerufene Methode in derselben JVM ausgeführt:

Wenn die [Business]-Schicht die create-Methode der [DAO]-Schicht aufruft, verfügt sie tatsächlich über einen Verweis auf den Parameter [Membership membership], den sie an die Methode übergibt.

Im Remote-Kontext werden die aufrufende und die aufgerufene Methode in unterschiedlichen JVMs ausgeführt:

Im obigen Beispiel läuft die [Business]-Schicht in JVM 1 und die [DAO]-Schicht in JVM 2 auf zwei verschiedenen Rechnern. Die beiden Schichten kommunizieren nicht direkt miteinander. Zwischen ihnen liegt 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 [Business]-Schicht wird so geschrieben, als würde sie in derselben JVM wie die [DAO]-Schicht laufen. Daher sind keine Codeänderungen erforderlich.

Der Kommunikationsmechanismus zwischen der [Business]-Schicht und der [DAO]-Schicht ist wie folgt:

  • Die [Business]-Schicht ruft die create-Methode der [DAO]-Schicht auf und übergibt ihr den Parameter [Contribution contribution1]
  • Dieser Parameter wird tatsächlich an die Übertragungsschicht [2] übergeben. Diese Schicht überträgt den Wert des Parameters „contribution1“ über das Netzwerk, nicht dessen Referenz. Die genaue Form dieses Werts hängt vom verwendeten Kommunikationsprotokoll ab.
  • Die empfangende [3]-Schicht ruft diesen Wert ab und verwendet ihn, um ein [Cotisation cotisation2]-Objekt zu rekonstruieren, das den ursprünglich von der [Business]-Schicht gesendeten Parameter widerspiegelt. Wir haben nun zwei (inhaltlich) identische Objekte in zwei verschiedenen JVMs: cotisation1 und cotisation2.
  • Die Präsentationsschicht übergibt das Objekt `contribution2` an die Methode `create` der [DAO]-Schicht, die es in der Datenbank persistiert. Nach diesem Vorgang wurde das Feld `id` des Objekts `contribution2` mit dem Primärschlüssel des Datensatzes initialisiert, der zur Tabelle [COTISATIONS] hinzugefügt wurde. Dies ist nicht der Fall für das Objekt `contribution1`, auf das die [business]-Schicht verweist. Wenn wir möchten, dass die [business]-Schicht auf das Objekt `contribution2` verweist, müssen wir es an die Schicht übergeben. Daher müssen wir die Signatur der Methode `create` in der [DAO]-Schicht ändern:
      // créer une nouvelle cotisation
Cotisation create(Cotisation cotisation);
  • Mit dieser neuen Signatur gibt die create-Methode das persistierte Objekt contribution2 zurück. Dieses Ergebnis wird an die empfangende Schicht [3] zurückgegeben, die die [DAO]-Schicht aufgerufen hat. Die [DAO]-Schicht gibt den Wert (nicht die Referenz) von contribution2 an die sendende Schicht [2] zurück.
  • Die sendende Schicht [2] ruft diesen Wert ab und verwendet ihn, um ein Objekt [Membership membership3] zu rekonstruieren, das das von der create-Methode der [DAO]-Schicht zurückgegebene Ergebnis widerspiegelt.
  • Das Objekt [Contribution contribution3] wird an die Methode in der [Business]-Schicht zurückgegeben, deren Aufruf der create-Methode der [DAO]-Schicht diesen gesamten Mechanismus ausgelöst hatte. Die [Business]-Schicht kann somit den Primärschlüsselwert ermitteln, der dem Objekt [Contribution contribution1] zugewiesen wurde, für das sie die Persistenz angefordert hatte: Dies ist der Wert des Feldes id in contribution3.

Die vorstehende Architektur ist nicht die gängigste. Häufiger befinden sich die [business]- und [DAO]-Schichten in derselben JVM:

In dieser Architektur sind es die Methoden der [Business]-Schicht, die Ergebnisse zurückgeben müssen, nicht die der [DAO]-Schicht. Dennoch lautet die folgende Signatur der create-Methode der [DAO]-Schicht:

      // créer une nouvelle cotisation
Cotisation create(Cotisation cotisation);

ermöglicht es uns, keine Annahmen über die tatsächlich vorhandene Architektur zu treffen. Die Verwendung von Signaturen, die unabhängig von der gewählten Architektur funktionieren – egal ob lokal oder remote –, bedeutet, dass, wenn eine aufgerufene Methode einige ihrer Parameter ändert:

  • Diese müssen ebenfalls Teil des Ergebnisses der aufgerufenen Methode sein
  • Die aufrufende Methode muss das Ergebnis der aufgerufenen Methode verwenden und nicht die Verweise auf die geänderten Parameter, die sie an die aufgerufene Methode übergeben hat.

Dies ermöglicht uns den Übergang von einer lokalen zu einer Remote-Architektur, ohne den Code ändern zu müssen. Betrachten wir die Schnittstelle [ICotisationDao] vor diesem Hintergrund noch einmal:

package dao;

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

public interface ICotisationDao {
       // create a new contribution
  public Cotisation create(Cotisation cotisation);
       // modify an existing contribution
  public Cotisation edit(Cotisation cotisation);
       // delete an existing contribution
  public void destroy(Cotisation cotisation);
       // search for a specific contribution
  public Cotisation find(Long id);
       // get all objects Contribution
  public List<Cotisation> findAll();

}
  • Zeile 8: Der Fall für die create-Methode 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 Cotisation-Objekt hat. Sie gibt das Objekt cotisation2 zurück, das eine Darstellung des geänderten Datensatzes ist. Der Parameter contribution1 selbst wird nicht geändert. Die Methode muss contribution2 als Ergebnis zurückgeben, unabhängig davon, ob es sich um eine Remote- oder eine lokale Architektur handelt.
  • Zeile 12: Die Methode „destroy“ löscht den Datensatz aus der Tabelle [COTISATIONS], der denselben Primärschlüssel wie das als Parameter übergebene Beitrag-Objekt hat. Das Beitrag-Objekt wird nicht verändert. Daher muss es nicht zurückgegeben werden.
  • Zeile 14: Der Parameter id der Methode find wird von der Methode nicht verändert. Er muss nicht im Ergebnis enthalten sein.
  • Zeile 16: Die Methode „findAll“ hat keine Parameter. Wir müssen sie daher nicht untersuchen.

Letztendlich muss nur die Signatur der create-Methode angepasst werden, damit sie in einer Remote-Architektur verwendet werden kann. Die obige Argumentation gilt auch für die anderen [DAO]-Schnittstellen. Wir werden sie hier nicht wiederholen, sondern stattdessen Signaturen verwenden, die sowohl in Remote- als auch in lokalen Architekturen einsetzbar sind.

Die [DAO]-Schnittstelle für den Zugriff auf [Indemnite]-Entitäten in der [INDEMNITES]-Tabelle sieht wie folgt aus:

package dao;

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

public interface IIndemniteDao {
     // create an Indemnity entity
  public Indemnite create(Indemnite indemnite);
     // modify an Indemnite entity
  public Indemnite edit(Indemnite indemnite);
     // delete an Indemnity entity
  public void destroy(Indemnite indemnite);
     // search for an Indemnite entity via its identifier
  public Indemnite find(Long id);
     // get all Indemnite entities
  public List<Indemnite> findAll();

}
  • Zeile 6: Die Schnittstelle [IIndemniteDao] verwaltet den Zugriff auf die Entität [Indemnite] und damit auf die Tabelle [INDEMNITES] in der Datenbank. Unsere Anwendung benötigt lediglich die Methode [findAll] in Zeile 16, die den gesamten Inhalt der Tabelle [INDEMNITES] abruft. Hier wollten wir einen allgemeineren Fall behandeln, in dem alle CRUD-Operationen (Create, Read, Update, Delete) an der Entität durchgeführt werden.
  • Zeile 8: Die Methode [create] erstellt eine neue [Indemnite]-Entität
  • Zeile 10: Die Methode [edit] ändert eine vorhandene [Indemnite]-Entität
  • Zeile 12: Die Methode [destroy] löscht eine vorhandene [Indemnite]-Entität
  • Zeile 14: Die Methode [find] ruft eine vorhandene [Indemnite]-Entität anhand ihrer ID ab
  • Zeile 16: Die Methode [findAll] gibt eine Liste aller vorhandenen [Indemnite]-Entitäten zurück

Die [DAO]-Schnittstelle für den Zugriff auf [Employe]-Entitäten in der [EMPLOYES]-Tabelle sieht wie folgt aus:

package dao;

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

public interface IEmployeDao {
     // create a new Employ entity
  public Employe create(Employe employe);
     // modify an existing Employe entity
  public Employe edit(Employe employe);
     // delete an Employ entity
  public void destroy(Employe employe);
     // search for an Employe entity via its id identifier
  public Employe find(Long id);
     // search for an Employe entity via its SS number
  public Employe find(String SS);
     // get all Employe entities
  public List<Employe> findAll();
}
  • Zeile 6: Die Schnittstelle [IEmployeDao] verwaltet den Zugriff auf die Entität [Employee] und damit auf die Tabelle [EMPLOYEES] in der Datenbank. Unsere Anwendung benötigt lediglich die Methode [findAll] in Zeile 16, die den gesamten Inhalt der Tabelle [EMPLOYEES] abruft. Hier wollten wir einen allgemeineren Fall behandeln, in dem alle CRUD-Operationen (Create, Read, Update, Delete) an der Entität durchgeführt werden.
  • Zeile 8: Die Methode [create] erstellt eine neue [Employee]-Entität
  • Zeile 10: Die Methode [edit] ändert eine vorhandene [Employee]-Entität
  • Zeile 12: Die Methode [destroy] löscht eine vorhandene [Employee]-Entität
  • Zeile 14: Die Methode [find] ruft eine vorhandene [Employee]-Entität über ihre ID ab
  • Zeile 16: Die Methode [find(String SS)] ruft eine vorhandene [Employee]-Entität anhand ihrer SS-Nummer ab. Wir haben gesehen, dass diese Methode für die Konsolenanwendung erforderlich war.
  • Zeile 18: Die Methode [findAll] gibt eine Liste aller vorhandenen [Employee]-Entitäten zurück. Wir haben gesehen, dass diese Methode für die grafische Anwendung notwendig war.

5.8. Die Klasse [PamException]

Die [DAO]-Schicht arbeitet mit der JDBC-API von Java. Diese API löst kontrollierte [SQLException]-Ausnahmen aus, die zwei Nachteile haben:

  • Sie blähen den Code auf, der diese Ausnahmen mithilfe von try/catch-Blöcken behandeln muss.
  • sie müssen in den Methodensignaturen der [IDao]-Schnittstelle mit „throws SQLException“ deklariert werden. Dies verhindert die Implementierung dieser Schnittstelle durch Klassen, die eine kontrollierte Ausnahme eines anderen Typs als [SQLException] auslösen würden.

Um dieses Problem zu beheben, wird die [DAO]-Schicht nur ungeprüfte Ausnahmen vom Typ [PamException] „weiterleiten“.

  • Die [JDBC]-Schicht löst Ausnahmen vom Typ [SQLException] aus
  • Die [JPA]-Schicht löst Ausnahmen aus, die für die verwendete JPA-Implementierung spezifisch sind
  • Die [DAO]-Schicht löst nicht abgefangene Ausnahmen vom Typ [PamException] aus

Dies hat zwei Konsequenzen:

  • Die [Business]-Schicht muss Ausnahmen aus der [DAO]-Schicht nicht mithilfe von try/catch-Blöcken behandeln. Sie kann diese einfach bis zur [UI]-Schicht weiterleiten.
  • Die Methoden der [IDao]-Schnittstelle müssen in ihren Signaturen nicht die Art der [PamException] angeben, was die Möglichkeit offen lässt, diese Schnittstelle mit Klassen zu implementieren, die einen anderen Typ von nicht abgefangener 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 {

   // error code
  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 and 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 daher um eine Ausnahmetyp, für den der Compiler nicht verlangt, dass wir ihn mit einem try/catch-Block behandeln oder in Methodensignaturen aufnehmen. Aus diesem Grund ist [PamException] nicht in den Methodensignaturen der Schnittstelle [IDao] enthalten. Dadurch kann die Schnittstelle von einer Klasse implementiert werden, die eine andere Art von Ausnahme auslöst, vorausgesetzt, diese leitet sich ebenfalls von [RuntimeException] ab.
  • Um zwischen den möglichen Fehlern zu unterscheiden, verwenden wir den Fehlercode in Zeile 7. Die drei Konstruktoren in den Zeilen 14, 19 und 24 sind die der übergeordneten Klasse [RuntimeException], denen wir einen Parameter hinzugefügt haben: den Fehlercode, den wir der Ausnahme zuweisen möchten.

Das Verhalten der Anwendung in Bezug auf Ausnahmen sieht wie folgt aus:

  • Die [DAO]-Schicht kapselt jede auftretende Ausnahme in eine [PamException] und wirft sie an die [business]-Schicht weiter.
  • Die [business]-Schicht lässt zu, dass von der [DAO]-Schicht ausgelöste Ausnahmen nach oben weitergeleitet werden. Sie verpackt jede in der [business]-Schicht auftretende Ausnahme in eine [PamException] und wirft sie an die [UI]-Schicht weiter.
  • Die [UI]-Schicht fängt alle Ausnahmen ab, die von der [business]- und der [DAO]-Schicht weitergeleitet werden. Sie zeigt die Ausnahme einfach auf der Konsole oder der grafischen Benutzeroberfläche an.

Betrachten wir nun nacheinander die Implementierung der [DAO]- und [Business]-Schichten.

5.9. Die [DAO]-Schicht der [PAM]-Anwendung

Wir arbeiten mit der folgenden Architektur:

5.9.1. Implementierung

Literaturempfehlung: Abschnitt 3.1.3 von [ref1]


Aufgabe: Schreiben Sie unter Verwendung der Spring/JPA-Integration die Klassen [CotisationDao, IndemniteDao, EmployeDao], um die Schnittstellen [ICotisationDao, IIndemniteDao, IEmployeDao] zu implementieren. Jede Klassenmethode fängt alle Ausnahmen ab und verpackt sie in eine [PamException] mit einem für die abgefangene Ausnahme spezifischen Fehlercode.


Die Implementierungsklassen werden Teil des Pakets [dao] sein:

  

5.9.2. Konfiguration

Literaturempfehlung: Abschnitt 3.1.5 in [ref1]

Die DAO/JPA-Integration wird über die Spring-Datei [spring-config-dao.xml] und die JPA-Datei [persistence.xml] konfiguriert:


Frage: Geben Sie den Inhalt dieser beiden Dateien an. Wir gehen davon aus, dass die verwendete Datenbank die MySQL5-Datenbank [dbpam_hibernate] ist, die durch das SQL-Skript [dbpam_hibernate.sql] generiert wurde. Die Spring-Datei definiert die folgenden drei Beans: employeDao vom Typ EmployeDao, indemniteDao vom Typ IndemniteDao und cotisationDao vom Typ CotisationDao. Außerdem wird als JPA-Implementierung Hibernate verwendet.


5.9.3. Tests

Empfohlene Lektüre: Abschnitte 3.1.6 und 3.1.7 von [ref1]

Nachdem die [DAO]-Schicht 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 [DAO]-Schicht erstellen. Diese werden im Paket [dao] [2] des Zweigs [Test Packages] [1] des NetBeans-Projekts abgelegt. Dieser Zweig ist nicht in dem Projekt enthalten, das mit der Option [Projekt erstellen] generiert wird, wodurch sichergestellt ist, dass die dort abgelegten Testprogramme nicht in die endgültige .jar-Datei des Projekts aufgenommen werden.

Die im Zweig [Test Packages] abgelegten Klassen haben Zugriff auf die Klassen im Zweig [Source Packages] sowie auf die Klassenbibliotheken des Projekts. Wenn die Tests andere Bibliotheken als die im Projekt enthaltenen benötigen, müssen diese im Zweig [Test Libraries] [2] deklariert werden.

Die Testklassen verwenden das JUnit-Unit-Test-Tool:

  • [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 Ergebnisse.

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(){
     // application configuration
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
     // layers DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
  }

  @Test
  public void initDB(){
     // fill the base
...
     // displays the contents of the database
...
  }

  @Before()
  public void clean(){
     // empty the base
...
  }
}
  • Die Methode [init] wird vor dem Start der Testsuite ausgeführt (Annotation @BeforeClass). Sie instanziiert die [DAO]-Schicht.
  • Die Methode [clean] wird vor jedem Test ausgeführt (Annotation @Before). Sie löscht die Datenbank.
  • Die Methode [initDB] ist ein Test (Annotation @Test). Es ist der einzige. Ein Test muss Assert.assertCondition-Assertionsanweisungen enthalten. Hier gibt es keine. Die Methode ist daher ein Dummy-Test. Ihr Zweck ist es, die Datenbank mit einigen Zeilen zu füllen und anschließend den Datenbankinhalt auf der Konsole anzuzeigen. Hier werden die Methoden create und findAll der [DAO]-Schichten verwendet.

Frage: Vervollständigen Sie den Code für die Klasse [JUnitInitDB]. Orientieren Sie sich dabei am Beispiel aus Abschnitt 3.1.6 von [ref1]. Der Code erzeugt die in Abschnitt 5.1 gezeigte Ausgabe.


5.9.5. Implementierung des Test- s

Wir sind nun bereit, [InitDB] auszuführen. Wir beschreiben die Vorgehensweise unter Verwendung des DBMS MySQL5:

  • Die Klassen [1], die Konfigurationsdateien [2] und die Testklassen der [DAO]-Schicht [3] sind eingerichtet,
  • das Projekt wird erstellt [4]
  • die Klasse [JUnitInitDB] wird ausgeführt [5]. Das DBMS MySQL5 wird mit einer vorhandenen Datenbank [dbpam_hibernate] gestartet,
  • das Fenster [Test Results] [6] zeigt an, dass die Tests erfolgreich waren. Diese Meldung ist hier nicht von Bedeutung, da das Programm [JUnitInitDB] keine Assert.assertCondition-Assertionsanweisungen enthält, die zum Fehlschlagen des Tests führen könnten. Dennoch zeigt sie, dass während der Testausführung keine Ausnahmen aufgetreten sind.

Das Fenster [Output] enthält die Ausführungsprotokolle, einschließlich derjenigen von Spring und des Tests selbst. Die von der Klasse [JUnitInitDB] generierte Ausgabe lautet 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 [EMPLOYEES, ALLOWANCES, CONTRIBUTIONS] wurden gefüllt. Dies kann überprüft werden, indem NetBeans mit der Datenbank [dbpam_hibernate] verbunden wird.

  • In [1] können Sie auf der Registerkarte [services] die Daten aus der Tabelle [employees] der Verbindung [dbpam_hibernate] [2] anzeigen,
  • in [3] das Ergebnis.

5.9.6. JUnitD- ao

Wir sehen uns nun eine zweite Testklasse [JUnitDao] an:

Das Klassenskelett sieht wie folgt aus:

package dao;

import exception.PamException;
...

public class JUnitDao {

// layers DAO
  static private IEmployeDao employeDao;
  static private IIndemniteDao indemniteDao;
  static private ICotisationDao cotisationDao;

  @BeforeClass
  public static void init() {
     // log
    log("init");
     // application configuration
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-dao.xml");
     // layers 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() {
...
  }

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

   // tests
  @Test
  public void test01() {
    log("test01");
     // list of contributions
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // we add a contribution
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // on demand
    cotisation = cotisationDao.find(cotisation.getId());
     // check
    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);
     // we modify it
    cotisation.setCsgrds(-1);
    cotisation.setCsgd(-1);
    cotisation.setRetraite(-1);
    cotisation.setSecu(-1);
    Cotisation cotisation2 = cotisationDao.edit(cotisation);
     // checks
    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);
     // the modified element is requested
    Cotisation cotisation3 = cotisationDao.find(cotisation2.getId());
     // checks
    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);
     // we delete the
    cotisationDao.destroy(cotisation3);
     // checks
    Cotisation cotisation4 = cotisationDao.find(cotisation3.getId());
    Assert.assertNull(cotisation4);
    cotisations = cotisationDao.findAll();
    Assert.assertEquals(nbCotisations, cotisations.size());
  }


  @Test
  public void test02(){
    log("test02");
     // we ask for the list of allowances
...
     // we add an Indemnite indemnite
..
     // fetch indemnity from base - recover indemnity1
..
     // we check that indemnity1 = indemnity
...
     // modify the indemnity obtained and persist the modification in BD. The result is indemnity2
 ...
     // check the indemnite2 version
    ...
     // search for indemnity2 in base - obtain indemnity3
    ...
     // check that compensation3 = compensation2
    ...
     // delete indemnite3 image in base
    ...
     // we'll search for indemnite3 in base
    ...
     // check that a null reference has been obtained
 ...
  }

  @Test
  public void test03(){
    log("test03");
     // we repeat a test analogous to the previous ones for Employe
 ...
  }

  @Test
  public void test04(){
    log("test04");
     // test method [IEmployeDao].find(String SS)
     // first with an existing employee
     // then with a non-existent employee
...
  }

  @Test
  public void test05(){
    log("test05");
     // we create two allowances with the same index
     // violates index uniqueness constraint
     // check for the occurrence of a PamException exception
     // and has the expected error no
...
  }

  @Test
  public void test06(){
    log("test06");
     // create two employees with the same SS number
     // violates the uniqueness constraint on n° SS
     // check for the occurrence of a PamException exception
     // and has the expected error no
...

  }

  @Test
  public void test07(){
    log("test07");
     // create two employees with the same SS number, the 1st with create, the 2nd with edit
     // violates the uniqueness constraint on n° SS
     // check for the occurrence of a PamException exception
     // and has the expected error no
...
  }

  @Test
  public void test08(){
    log("test08");
     // deleting a non-existent employee does not trigger an exception
     // it is added and then destroyed - it is checked
...
  }

  @Test
  public void test09(){
    log("test09");
     // modifying an employee without having the correct version should trigger an exception
     // we check it
...
  }

  @Test
  public void test10(){
    log("test10");
     // deleting an employee without having the correct version should trigger an exception
     // we check it
...

  }

   // getters and setters
  ...
}

In der vorherigen Testklasse wird die Datenbank vor jedem Test gelöscht.


Frage: Schreiben Sie die folgenden Methoden:

1 – test02: basierend auf test01

2 – test03: Ein Mitarbeiter hat ein Feld vom Typ „Entschädigung“. Erstellen Sie daher eine Entität „Entschädigung“ und eine Entität „Mitarbeiter“

3 – test04.


Wenn wir genauso vorgehen wie bei der Testklasse [JUnitInitDB], erhalten wir folgende Ergebnisse:

  • In [1] führen wir die Testklasse
  • in [2] die Testergebnisse im Fenster [Test Results]

Lassen Sie uns einen Fehler auslösen, um zu sehen, wie er auf der Ergebnisseite gemeldet wird:

  @Test
  public void test01() {
    log("test01");
     // list of contributions
    List<Cotisation> cotisations = cotisationDao.findAll();
    int nbCotisations = cotisations.size();
     // we add a contribution
    Cotisation cotisation = cotisationDao.create(new Cotisation(3.49, 6.15, 9.39, 7.88));
     // on demand
    cotisation = cotisationDao.find(cotisation.getId());
     // check
    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);
     // we modify it
....
}

Zeile 13: Die Überprüfung führt zu einem Fehler, 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] finden Sie eine Zusammenfassung der Ausnahme, die zum Fehlschlagen des Tests geführt hat. Sie enthält die Zeilennummer im Java-Code, an der die Ausnahme aufgetreten ist.

5.10. Die [Business]-Schicht der [PAM]-Anwendung

Nachdem nun die [DAO]-Schicht geschrieben wurde, wenden wir uns der Untersuchung der Business-Schicht [2] zu:

5.10.1. Die Java-Schnittstelle [IMetier]

Dies wurde in Abschnitt 5.7 beschrieben. Wir fassen es im Folgenden noch einmal zusammen:

package metier;

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

public interface IMetier {
   // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS, double nbHeuresTravaillées, int nbJoursTravaillés );
   // list of employees
  public List<Employe> findAllEmployes();
}

Die Implementierung der [Business]-Schicht erfolgt in einem [Business]-Paket:

 

Das [Business]-Paket wird neben der [IMetier]-Schnittstelle und ihrer Implementierung [Metier] zwei weitere Klassen enthalten: [Payroll] und [PayrollItems]. Die Klasse [Payroll] wurde in Abschnitt 5.7 kurz vorgestellt. Wir werden nun darauf zurückkommen.

5.10.2. Die Klasse [Payroll]

Die Methode [calculatePayStub] der Schnittstelle [IMetier] gibt ein Objekt vom Typ [PayStub] 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 fields
  private Employe employe;
  private Cotisation cotisation;
  private ElementsSalaire elementsSalaire;

   // manufacturers
  public FeuilleSalaire() {

  }

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

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

   // accessors
...  
}
  • 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, die an den Index des Arbeitnehmers gekoppelt sind
  • Zeile 12: die Bestandteile ihres Gehalts
  • Zeilen 14–22: die beiden Konstruktoren der Klasse
  • Zeilen 25–27: [toString]-Methode zur Identifizierung eines bestimmten [PayStub]-Objekts
  • Zeile 29 und folgende: öffentliche Zugriffsmethoden auf die privaten Felder der Klasse

Die Klasse [ElementsSalaire], auf die in Zeile 11 der oben genannten Klasse [FeuilleSalaire] verwiesen wird, enthält die Elemente, aus denen sich eine Gehaltsabrechnung zusammensetzt. Ihre Definition lautet wie folgt:

package metier;

public class ElementsSalaire implements Serializable{

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

   // manufacturers
  public ElementsSalaire() {

  }

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

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

   // public accessors
...  
}
  • Zeile 3: Die Klasse implementiert die Schnittstelle „Serializable“, da sie eine Komponente der „PayrollClass“ ist, die serialisierbar sein muss.
  • Zeile 6: das Grundgehalt
  • Zeile 7: Sozialversicherungsbeiträge, die auf dieses Grundgehalt gezahlt werden
  • Zeile 8: Tägliche Unterhaltszahlungen für Kinder
  • Zeile 9: die täglichen Verpflegungszuschüsse für Kinder
  • Zeile 10: das an die Kinderbetreuungskraft zu zahlende Nettogehalt
  • Zeilen 12–24: Klassenkonstruktoren
  • Zeilen 27–31: [toString]-Methode zur Identifizierung eines bestimmten [ElementsSalaire]-Objekts
  • Zeilen 34 ff.: öffentliche Zugriffsmethoden auf die privaten Felder der Klasse

5.10.3. Die Implementierungsklasse [Metier] der [Business]-Schicht

Die Implementierungsklasse [Metier] der [Business]-Schicht könnte wie folgt aussehen:

package metier;

...

@Transactional
public class Metier implements IMetier {

   // reference on the [DAO] layer
  private ICotisationDao cotisationDao = null;
  private IEmployeDao employeDao=null;


   // get your payslip
  public FeuilleSalaire calculerFeuilleSalaire(String SS,
    double nbHeuresTravaillées, int nbJoursTravaillés) {
...
  }

   // list of employees
   public List<Employe> findAllEmployes() {
     ...
  }

   // getters and setters
...
 }
  • Zeile 5: Die Spring-Annotation @Transactional stellt sicher, dass jede Methode in der Klasse innerhalb einer Transaktion ausgeführt wird.
  • Zeilen 9–10: Verweise auf die [DAO]-Schichten der Entitäten [Cotisation, Employe, Indemnite]
  • Zeilen 14–17: die Methode [calculatePayroll]
  • Zeilen 20–22: die Methode [findAllEmployees]
  • Zeile 24 und folgende: die öffentlichen Zugriffsmethoden für die privaten Felder der Klasse

Frage: Schreiben Sie den Code für die Methode [findAllEmployees].



Frage: Schreiben Sie den Code für die Methode [calculatePayroll].


Beachten Sie folgende Punkte:

  • Die Methode zur Berechnung des Gehalts wurde in Abschnitt 5.2 erläutert.
  • Wenn der Parameter [SS] keinem Mitarbeiter entspricht (die [DAO]-Schicht hat einen Null-Zeiger zurückgegeben), löst die Methode eine [PamException] mit einem entsprechenden Fehlercode aus.

5.10.4. Testen der [business]-Schicht

Wir erstellen zwei Testprogramme:

Die Testklassen [3] werden im Paket [metier] [2] innerhalb des Zweigs [Test Packages] [1] des Projekts erstellt.

Die Klasse [JUnitMetier_1] könnte wie folgt aussehen:

package metier;

...

public class JUnitMetier_1 {

// business layer
  private IMetier metier;

  @BeforeClass
  public void init(){
     // log
    log("init");
     // application configuration
     // instantiation layer [metier]
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-metier-dao.xml");
    metier = (IMetier) ctx.getBean("metier");
     // layers DAO
    IEmployeDao employeDao=(IEmployeDao) ctx.getBean("employeDao");
    ICotisationDao cotisationDao=(ICotisationDao) ctx.getBean("cotisationDao");
    IIndemniteDao indemniteDao=(IIndemniteDao) ctx.getBean("indemniteDao");
     // empty the base
    for(Employe employe:employeDao.findAll()){
      employeDao.destroy(employe);
    }
    for(Cotisation cotisation:cotisationDao.findAll()){
      cotisationDao.destroy(cotisation);
    }
    for(Indemnite indemnite : indemniteDao.findAll()){
      indemniteDao.destroy(indemnite);
    }
     // fill it
    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));
  }

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

   // test
  @Test
  public void test01(){
     // wage sheet calculation
    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()));
    }
  }
}

Die Klasse enthält keine Assert.assertCondition-Prüfungen. Wir versuchen lediglich, einige Gehälter zu berechnen, um sie anschließend manuell zu überprüfen. Die Bildschirmausgabe, die durch Ausführen der vorherigen Klasse erhalten wird, lautet wie folgt:

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: Gehaltsabrechnung von Justine Laverti
  • Zeile 5: Lohnabrechnung von Marie Jouveinal
  • Zeile 6: Die Ausnahme aufgrund der Tatsache, dass der Mitarbeiter mit der Sozialversicherungsnummer „xx“ nicht existiert.

Frage: Zeile 17 von [JUnitMetier_1] verwendet das Spring-Bean namens „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 {

// business layer
  private IMetier metier;

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

   // logs
  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], mit dem Unterschied, dass diesmal die Assertions in die Methode test01 eingefügt wurden.


Frage: 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 [ui]-Schicht der [PAM]-Anwendung – Version -Konsole

Nachdem nun die [business]-Schicht geschrieben wurde, müssen wir noch die [ui]-Schicht schreiben [1]:

Wir werden zwei verschiedene Implementierungen der [ui]-Schicht erstellen: eine Konsolenversion und eine Swing-GUI-Version:

5.11.1. Die Klasse [ ui.console.Main]

Wir konzentrieren uns zunächst auf 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) {
     // local data
    final String syntaxe = "pg num_securite_sociale nb_heures_travaillées nb_jours_travaillés";
     // check the number of parameters
...
     // error list
    ArrayList erreurs = new ArrayList();
     // the second parameter must be a real number >0
...
     // mistake?
    if (...) {
      erreurs.add("Le nombre d'heures travaillées [" + args[1]
        + "] est erroné");
    }
     // the third parameter must be an integer >0
...
     // mistake?
    if (...) {
      erreurs.add("Le nombre de jours travaillés [" + args[2]
        + "] est erroné");
    }
     // mistakes?
    if (erreurs.size() != 0) {
      for (int i = 0; i < erreurs.size(); i++) {
        System.err.println(erreurs.get(i));
      }
      return;
    }
     // it's OK - we can ask for the payslip
    FeuilleSalaire feuilleSalaire = null;
    try {
       // instantiation layer [metier]
      ...
       // wage sheet calculation
      ...
    } 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;
    }

     // detailed display
    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";
  }
}

Frage: Vervollständigen Sie den obigen Code.


5.11.2. Ausführung

Um die Klasse [ui.console.Main] auszuführen, gehen Sie wie folgt vor:

  • Wählen Sie in [1] die Projekteigenschaften aus,
  • wählen Sie in [2] die Eigenschaft [Ausführen] des Projekts aus,
  • verwenden Sie die Schaltfläche [3], um die auszuführende Klasse (die sogenannte Hauptklasse) anzugeben,
  • wählen Sie die Klasse [4] aus,
  • Die Klasse ist in [5] aufgeführt. Diese Klasse benötigt drei Argumente zur Ausführung (SS-Nummer, Anzahl der geleisteten Arbeitsstunden, Anzahl der Arbeitstage). Diese Argumente werden in [6] eingegeben,
  • sobald dies geschehen ist, kann das Projekt ausgeführt werden [7]. Die vorstehende Konfiguration bedeutet, dass die Klasse [ui.console.Main] ausgeführt wird.

Die Ergebnisse der Ausführung werden im Fenster [output] angezeigt:

5.12. Die [ui]-Ebene der [PAM]-Anwendung – grafische Version

Wir werden nun die [ui]-Ebene mit einer grafischen Benutzeroberfläche implementieren:

  • in [1], die Klasse [PamJFrame] der grafischen Benutzeroberfläche
  • in [2]: die grafische Benutzeroberfläche

5.12.1. Ein kurzes Tutorial

Um die grafische Benutzeroberfläche zu erstellen, gehen Sie wie folgt vor:

  • [1]: Erstellen Sie eine neue Datei über die Schaltfläche [1] [Neue Datei...]
  • [2]: Wählen Sie die Dateikategorie [Swing-GUI-Formulare], d. h. grafische Formulare
  • [3]: Wählen Sie den Typ [JFrame-Formular], einen leeren Formulartyp
  • [5]: Benennen Sie das Formular; dies ist gleichzeitig der Klassenname
  • [6]: Ordne das Formular einem Paket zu
  • [8]: Das Formular wird zur Projektstruktur hinzugefügt
  • [9]: Auf das Formular kann über zwei Ansichten zugegriffen werden: [Design] [9], in der Sie die verschiedenen Komponenten des Formulars entwerfen können, und [Source] [10 unten], die Zugriff auf den Java-Code des Formulars bietet. Letztendlich ist ein Formular eine Java-Klasse wie jede andere auch. Die Ansicht [Design] ist ein Werkzeug zum Entwerfen des Formulars. Jedes Mal, wenn im Modus [Design] eine Komponente hinzugefügt wird, wird in der Ansicht [Quelle] entsprechender Java-Code hinzugefügt.
  • [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 anderen im Zweig [Other Components].
  • In [13] wählen wir mit einem einzigen Klick eine [JLabel]-Komponente aus
  • In [14] ziehen wir sie im [Design]-Modus auf das Formular
  • In [15] definieren wir die Eigenschaften des JLabel (Text, Schriftart).
  • In [16] das Ergebnis.
  • In [17] fordern wir eine Vorschau des Formulars an
  • In [18] das Ergebnis
  • In [19] wurde das Label [JLabel1] zum Komponentenbaum im [Inspector]-Fenster hinzugefügt
  • In [20] und [21]: In der Ansicht [Source] des Formulars wurde Java-Code hinzugefügt, um das hinzugefügte JLabel zu verarbeiten.

Ein Tutorial zum Erstellen von Formularen mit NetBeans ist unter der URL [http://www.netbeans.org/kb/trails/matisse.html] verfügbar.

5.12.2. Die [PamJFrame]-GUI

Wir werden die folgende grafische Benutzeroberfläche erstellen:

  • in [1], die grafische Benutzeroberfläche
  • in [2] die Baumstruktur ihrer Komponenten: ein JLabel und sechs JPanel-Container

JLabel1

JPanel1

JPanel2

JPanel3

JPanel4

JPanel5


Praxisübung: Erstellen Sie die zuvor beschriebene grafische Benutzeroberfläche mithilfe des Tutorials [http://www.netbeans.org/kb/trails/matisse.html].


5.12.3. Ereignisse der grafischen Benutzeroberfläche

Empfohlene Lektüre: das Kapitel [Grafische Benutzeroberflächen] in [ref2].

Wir werden den Klick auf die Schaltfläche [jButtonSalaire] behandeln. Um die Methode zur Behandlung 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 add your handling code here:
}

Der Java-Code, der die vorherige 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 legen fest, dass der Klick (evt vom Typ ActionPerformed) auf die Schaltfläche [jButtonSalaire] (Zeile 2) von der Methode [jButtonSalaireActionPerformed] (Zeile 4) verarbeitet werden muss.

Wir werden auch das [caretUpdate]-Ereignis (Cursorbewegung) im Eingabefeld [jTextFieldHT] behandeln. Um den Handler für dieses Ereignis zu erstellen, gehen wir wie zuvor vor:

Der Handler für das [caretUpdate]-Ereignis im Eingabefeld [jTextFieldHT] wird generiert:

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

Der Java-Code, der die vorherige Methode an das [caretUpdate]-Ereignis des Textfelds [jTextFieldHT] bindet, 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 [caretUpdate]-Ereignis (Zeile 2) des [jTextFieldHT]-Buttons (Zeile 1) von der [jTextFieldHTCaretUpdate]-Methode (Zeile 3) verarbeitet werden soll.

5.12.4. Initialisierung der GUI

Kommen wir zurück zur Architektur unserer Anwendung:

Die [ui]-Schicht benötigt eine Referenz auf die [business]-Schicht. Erinnern wir uns daran, wie diese Referenz in der Konsolenanwendung abgerufen wurde:

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

Die Vorgehensweise ist in der GUI-Anwendung identisch. Bei der Initialisierung der GUI-Anwendung muss auch die Referenz [IMetier metier] aus Zeile 3 oben initialisiert werden. Der für die GUI generierte Code lautet derzeit wie folgt:

package ui.swing;

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

   /** Creates new form PamJFrame */
  public PamJFrame() {
    initComponents();
  }

  /** This method is called from within the constructor to
   * 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=" Generated 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);
      }
    });
  }

  // Variables declaration - do not modify
  private javax.swing.JButton jButtonSalaire;
...
   // End of variables declaration

}
  • Zeilen 29–35: die statische Methode [main], die die Anwendung startet
  • Zeile 32: Eine Instanz der GUI [PamJFrame] wird erstellt und sichtbar gemacht.
  • Zeilen 7–9: Der GUI-Konstruktor.
  • Zeile 8: Aufruf der in Zeile 17 definierten Methode [initComponents]. Diese Methode wird automatisch auf der Grundlage der im [Design]-Modus durchgeführten Arbeit generiert. Ändern Sie sie nicht.
  • Zeile 21: Die Methode, die die Bewegung des Eingabecursors im Feld [jTextFieldHT] übernimmt
  • Zeile 25: Die Methode, die den Klick auf die Schaltfläche [jButtonSalaire] verarbeitet

Um dem obigen Code eigene Initialisierungen hinzuzufügen, können wir wie folgt vorgehen:

  /** Creates new form PamJFrame */
  public PamJFrame() {
    initComponents();
    doMyInit();
  }

...

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

   // proprietary initializations
  public void doMyInit(){
     // init context
    try{
       // instantiation layer [metier]
...
     // list of employees
...
    }catch (PamException ex){
     // the exception message is placed in [jTextAreaStatus]
...
     // return
      return;
    }
     // salary button disabled
...
     // jScrollPane1 hidden
...
     // spinner days worked
    jSpinnerJT.setModel(new SpinnerNumberModel(0,0,31,1));
     // combobox employees
    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 werden durch den Code in den Zeilen 10–42 definiert

Frage: Vervollständigen Sie den Code für die Prozedur [doMyInit] anhand der Kommentare.


5.12.5. Ereignisbehandler


Frage: Schreiben Sie die Methode [jTextFieldHTCaretUpdate]. Diese Methode muss sicherstellen, dass die Schaltfläche [jButtonSalaire] deaktiviert wird, wenn die Daten im Feld [jTextFieldHT] keine reelle Zahl >=0 sind.



Frage: Schreiben Sie die Methode [jButtonSalaireActionPerformed], die die Gehaltsabrechnung für den in [jComboBoxEmployes] ausgewählten Mitarbeiter anzeigen muss.


5.12.6. Ausführen der GUI

Um die GUI auszuführen, ändern Sie die [Run]-Konfiguration des Projekts:

  • Geben Sie unter [1] die GUI-Klasse ein

Das Projekt muss vollständig sein und die Konfigurationsdateien (persistence.xml, spring-config-metier-dao.xml) sowie die GUI-Klasse enthalten. Starten Sie das Ziel-DBMS, bevor Sie das Projekt ausführen.

Wir interessieren uns für die folgende Architektur, bei der die JPA-Schicht nun durch EclipseLink implementiert wird:

5.13.1. Das NetBeans-Projekt

Das neue NetBeans-Projekt wird durch Kopieren des vorherigen Projekts erstellt:

  • in [1]: Klicken Sie mit der rechten Maustaste auf das Hibernate-Projekt und wählen Sie „Kopieren“
  • und wählen Sie über die Schaltfläche [2] den übergeordneten Ordner für das neue Projekt aus. Der Ordnername erscheint in [3].
  • Geben Sie in [4] einen Namen für das neue Projekt ein
  • in [5] den Namen des Projektordners
  • In [1] wurde das neue Projekt erstellt. Es hat denselben Namen wie das ursprüngliche Projekt
  • in [2] und [3], benennen Sie es in [mv-pam-spring-eclipselink] um.

Das Projekt muss an zwei Stellen geändert werden, um es an die neue JPA-/EclipseLink-Schicht anzupassen:

  1. In [4] müssen die Spring-Konfigurationsdateien geändert werden. Hier befindet sich die Konfiguration der JPA-Schicht.
  2. In [5] müssen die Projektbibliotheken geändert werden: Die Hibernate-Bibliotheken müssen durch die von EclipseLink ersetzt werden.

Beginnen wir mit dem letztgenannten 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: Abhängigkeiten für die EclipseLink-JPA-Implementierung,
  • Zeilen 19–24: das Maven-Repository für EclipseLink.

Die Spring-Konfigurationsdateien müssen geändert werden, um anzugeben, dass sich die JPA-Implementierung geändert hat. In beiden Dateien ändert sich nur der Abschnitt, der die JPA-Schicht 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">

   <!-- application layers -->
   <!- - DAO -->
  <bean id="employeDao" class="dao.EmployeDao" />
  <bean id="indemniteDao" class="dao.IndemniteDao" />
  <bean id="cotisationDao" class="dao.CotisationDao" />
   <!-- business -->
  <bean id="metier" class="metier.Metier">
    <property name="employeDao" ref="employeDao"/>
    <property name="indemniteDao" ref="indemniteDao"/>
    <property name="cotisationDao" ref="cotisationDao"/>  
  </bean>

   <!-- configuration JPA -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <!--
          <property name="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>

   <!-- data source 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>

In den Zeilen 19–36 wird die JPA-Schicht konfiguriert. Als JPA-Implementierung wird Hibernate verwendet (Zeile 22). Außerdem ist die Zieldatenbank [dbpam_hibernate] (Zeile 41).

Um zu einer JPA/EclipseLink-Implementierung zu wechseln, werden die obigen Zeilen 19–35 durch die folgenden Zeilen ersetzt:

  <!-- configuration JPA -->
  <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
      <bean class="org.springframework.orm.jpa.vendor.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 JPA-Implementierung ist EclipseLink
  • Zeile 9: Die Eigenschaft „databasePlatform“ legt das Ziel-DBMS fest, in diesem Fall MySQL
  • Zeile 11: Zum Generieren der Datenbanktabellen bei der Instanziierung der JPA-Schicht. Hier ist die Eigenschaft auskommentiert.
  • Zeile 7: Zur Anzeige der von der JPA-Schicht ausgegebenen SQL-Anweisungen auf der Konsole. Hier ist die Eigenschaft auskommentiert.

Zusätzlich wird die Zieldatenbank zu [dbpam_eclipselink] (Zeile 4 unten):

1
2
3
4
5
6
7
8
9
<!-- data source 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. Ausführen der Tests

Bevor die gesamte Anwendung getestet wird, empfiehlt es sich, zu überprüfen, ob die JUnit-Tests mit der neuen JPA-Implementierung erfolgreich durchlaufen werden. Bevor wir sie ausführen, löschen wir zunächst die Tabellen aus der Datenbank. Erstellen Sie dazu bei Bedarf auf der Registerkarte [Runtime] in NetBeans eine Verbindung zur Datenbank dbpam_eclipselink / MySQL5. Sobald die Verbindung zur Datenbank dbpam_eclipselink / MySQL5 hergestellt ist, können Sie wie unten gezeigt mit dem Löschen der Tabellen fortfahren:

  • [1]: vor dem Löschen
  • [2]: nach dem Löschen

Sobald dies erledigt ist, können Sie den ersten Test auf der [DAO]-Ebene ausführen: InitDB, der die Datenbank befüllt. Um sicherzustellen, dass die zuvor gelöschten Tabellen von der Anwendung neu erstellt werden, stellen Sie sicher, dass in der Spring JPA-/EclipseLink-Konfiguration die Zeile:

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

vorhanden ist und nicht auskommentiert ist.

Wir erstellen das Projekt und führen dann den [JUnit- -InitDB]-Test aus:

  • In [1] wird der InitDB-Test ausgeführt.
  • In [2] schlägt er fehl. Die Ausnahme wird von Spring ausgelöst und nicht von einem fehlgeschlagenen Test.

Ursache: org.springframework.beans.factory.BeanCreationException: Fehler beim Erstellen der Bean mit dem Namen „entityManagerFactory“, definiert in der Klassenpfad-Ressource [spring-config-DAO.xml]: Aufruf der init-Methode fehlgeschlagen; verschachtelte Ausnahme ist java.lang.IllegalStateException: Muss mit Java-Agent gestartet werden, um InstrumentationLoadTimeWeaver zu verwenden. Siehe Spring-Dokumentation.

Spring weist darauf hin, dass ein Konfigurationsproblem vorliegt. 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 die JVM, auf der die Anwendung läuft, mit einem bestimmten Parameter, einem Java-Agenten, gestartet werden. Das Format dieses Parameters lautet wie folgt:

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

[spring-agent.jar] ist der Java-Agent, den die JVM benötigt, um die Spring/EclipseLink-Konfiguration zu verwalten.

Beim Ausführen eines Projekts ist es möglich, Argumente an die JVM zu übergeben:

  • In [1] können Sie auf die Projekteigenschaften zugreifen
  • In [2] die Ausführungseigenschaften
  • In [3] übergeben Sie den Parameter -javaagent an die JVM

5.13.3. InitDB

Nun können wir [InitDB] erneut testen. Diesmal lauten die Ergebnisse wie folgt:

  • In [1] war der Test erfolgreich
  • In [2] aktualisieren wir auf der Registerkarte [Services] die Verbindung von NetBeans zur Datenbank [dbpam_eclipselink]
  • In [3] wurden vier Tabellen erstellt
  • In [5] zeigen wir den Inhalt der Tabelle [employees] an
  • in [6] das Ergebnis.

5.13.4. JUnitDao

Die Ausführung der Testklasse [JUnitDao] kann fehlschlagen, obwohl sie mit der JPA/Hibernate-Implementierung erfolgreich war. Um zu verstehen, warum das so ist, wollen wir ein Beispiel analysieren.

Die zu testende Methode ist die folgende IndemniteDao.create-Methode:

package dao;

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

  @PersistenceContext
  private EntityManager em;

   // manufacturer
  public IndemniteDao() {
  }

   // create an allowance
  public Indemnite create(Indemnite indemnite) {
    try{
      em.persist(indemnite);
    }catch(Throwable th){
      throw new PamException(th,31);
    }
    return indemnite;
  }

...
}
  • Zeilen 15–22: die zu testende Methode

Die Testmethode lautet wie folgt:


package dao;
 
...
 
public class JUnitDao {
 
// layers DAO
  static private IEmployeDao employeDao;
  static private IIndemniteDao indemniteDao;
  static private ICotisationDao cotisationDao;
 
  @BeforeClass
  public static void init() {
    // log
    log("init");
    // application configuration
    ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config-DAO.xml");
    // layers DAO
    employeDao = (IEmployeDao) ctx.getBean("employeDao");
    indemniteDao = (IIndemniteDao) ctx.getBean("indemniteDao");
    cotisationDao = (ICotisationDao) ctx.getBean("cotisationDao");
  }
 
  @Before()
  public void clean() {
    // empty the base
    for (Employe employe : employeDao.findAll()) {
      employeDao.destroy(employe);
    }
    for (Cotisation cotisation : cotisationDao.findAll()) {
      cotisationDao.destroy(cotisation);
    }
    for (Indemnite indemnite : indemniteDao.findAll()) {
      indemniteDao.destroy(indemnite);
    }
  }
 
  // logs
  private static void log(String message) {
    System.out.println("----------- " + message);
  }
 
  // tests
….
  @Test
  public void test05() {
    log("test05");
    // we create two allowances with the same index
    // violates index uniqueness constraint
    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;
      // checks
      Assert.assertEquals(31, ex.getCode());
    } catch (Throwable th1) {
      th = th1;
    }
    // checks
    Assert.assertTrue(erreur);
    // exception chain
    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());
    }
    // the 1st allowance had to be continued
    Indemnite indemnite = indemniteDao.find(indemnite1.getId());
    // check
    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);
    // the second indemnity should not have persisted
    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 ist erfolgreich, was bedeutet, dass die Assertions verifiziert wurden und keine Ausnahme aus der Testmethode ausgelöst wurde.


Frage: Erkläre, was passiert ist.


Die mit einer JPA/EclipseLink-Schicht erzielten Ergebnisse lauten wie folgt:

----------- test05
[EL Warning]: 2010-06-04 16:48:26.421--UnitOfWork(749304)--Exception [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 ist der Test erfolgreich, was bedeutet, dass die Assertions verifiziert wurden und keine Ausnahme aus der Testmethode ausgelöst wird.


Frage: Erklären Sie, was passiert ist.



Frage: Was können wir aus diesen beiden Beispielen über die Austauschbarkeit von JPA-Implementierungen schließen? Ist sie hier vollständig gegeben?


5.13.5. Die anderen Tests

Sobald die [DAO]-Schicht getestet und für korrekt befunden wurde, können wir mit dem Testen der [Business]-Schicht und des Projekts selbst in seiner Konsolen- oder grafischen Version fortfahren. Eine Änderung der JPA-Implementierung hat keine Auswirkungen auf die [Business]- und [UI]-Schichten; wenn diese Schichten also mit Hibernate funktioniert haben, werden sie auch mit EclipseLink funktionieren, mit wenigen Ausnahmen: Das vorherige Beispiel zeigt, dass die von den [DAO]-Schichten ausgelösten Ausnahmen unterschiedlich sein können. Im Testfall löst Spring / JPA / Hibernate also eine [PamException] aus, eine für die [pam]-Anwendung spezifische Ausnahme, während Spring / JPA / EclipseLink eine [TransactionSystemException] auslöst, eine Ausnahme aus dem Spring-Framework. Wenn die [ui]-Schicht im Testfall eine [PamException] erwartet, da sie mit Hibernate erstellt wurde, funktioniert sie beim Wechsel zu EclipseLink nicht mehr.

5.13.6. Zu erledigende Aufgaben


Praktische Aufgabe: Testen Sie die Konsolen- und Swing-Anwendungen erneut mit verschiedenen DBMS: MySQL5, Oracle XE, SQL Server.