4. JPA „ “ – eine Zusammenfassung
Wir möchten JPA (Java Persistence API) anhand einiger Beispiele vorstellen. JPA wird im Kurs behandelt:
- Java-Persistenz 5 in der Praxis: [http://tahe.developpez.com/java/jpa] – vermittelt die Werkzeuge zum Aufbau der Datenzugriffsschicht mit JPA
4.1. Die Rolle von JPA in einer mehrschichtigen Architektur
Der Leser wird gebeten, den Anfang dieses Dokuments (Absatz 2) noch einmal zu lesen, in dem die Rolle der Schicht JPA in einer Schichtenarchitektur erläutert wird. Die Schicht JPA fügt sich in die Datenzugriffsschichten ein:
![]() |
Die Schicht [DAO] kommuniziert mit der Spezifikation JPA. Unabhängig davon, welches Produkt diese Spezifikation implementiert, bleibt die Schnittstelle der Schicht JPA, die der Schicht [DAO] zur Verfügung gestellt wird, unverändert. Im Folgenden stellen wir einige Beispiele aus [ref1] vor, anhand derer wir unsere eigene Schicht JPA erstellen können.
4.2. JPA – Beispiele
4.2.1. Beispiel 1 – Objektdarstellung einer einzelnen Tabelle
4.2.1.1. Die Tabelle [personne]
Betrachten wir eine Datenbank mit einer einzigen Tabelle [personne], deren Aufgabe darin besteht, einige Informationen über Personen zu speichern:
![]() |
Primärschlüssel der Tabelle | |
Versionsnummer des Datensatzes in der Tabelle. Bei jeder Änderung der Person wird ihre Versionsnummer erhöht. | |
Name der Person | |
ihr Vorname | |
Geburtsdatum | |
Ganzzahl 0 (ledig) oder 1 (verheiratet) | |
Anzahl der Kinder der Person |
4.2.1.2. Die Entität [Personne]
Wir befinden uns in der folgenden Ausführungsumgebung:
![]() |
Die Schicht JPA [5] soll eine Brücke zwischen der relationalen Welt der Datenbank [7] und der Objektwelt [4] schlagen, die von den Java-Programmen [3] verarbeitet wird. Diese Verbindung wird über die Konfiguration hergestellt, und es gibt zwei Möglichkeiten, dies zu tun:
- mit XML-Dateien. Dies war bis zur Einführung von JDK 1.5
- mit Java-Annotationen seit JDK 1.5
In diesem Dokument werden wir ausschließlich die zweite Methode verwenden.
Das [Personne]-Objekt, das die zuvor vorgestellte Tabelle [personne] abbildet, könnte wie folgt aussehen:
...
@SuppressWarnings("unused")
@Entity
@Table(name="Personne")
public class Personne implements Serializable{
@Id
@Column(name = "ID", nullable = false)
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column(name = "VERSION", nullable = false)
@Version
private int version;
@Column(name = "NOM", length = 30, nullable = false, unique = true)
private String nom;
@Column(name = "PRENOM", length = 30, nullable = false)
private String prenom;
@Column(name = "DATENAISSANCE", nullable = false)
@Temporal(TemporalType.DATE)
private Date datenaissance;
@Column(name = "MARIE", nullable = false)
private boolean marie;
@Column(name = "NBENFANTS", nullable = false)
private int nbenfants;
// Hersteller
public Personne() {
}
public Personne(String nom, String prenom, Date datenaissance, boolean marie,
int nbenfants) {
setNom(nom);
setPrenom(prenom);
setDatenaissance(datenaissance);
setMarie(marie);
setNbenfants(nbenfants);
}
// toString
public String toString() {
...
}
// Getter und Setter
...
}
Die Konfiguration erfolgt mithilfe von Java-Annotationen @Annotation. Java-Annotationen werden entweder vom Compiler oder von speziellen Tools zur Laufzeit ausgewertet. Abgesehen von der Annotation in Zeile 3, die für den Compiler bestimmt ist, sind alle Annotationen hier für die verwendete Implementierung JPA, Hibernate oder Toplink vorgesehen. Sie werden daher zur Laufzeit ausgewertet. Fehlen die Tools, die sie interpretieren können, werden diese Annotationen ignoriert. Somit könnte die oben genannte Klasse [Personne] in einem Kontext außerhalb von JPA verwendet werden.
Es sind zwei Anwendungsfälle für die Annotationen JPA in einer Klasse C zu unterscheiden, die einer Tabelle T zugeordnet ist:
- Die Tabelle T existiert bereits: Die Annotationen JPA müssen dann die vorhandenen Daten wiedergeben (Name und Definition der Spalten, Integritätsbeschränkungen, Fremdschlüssel, Primärschlüssel usw.).
- Die Tabelle T existiert nicht und wird anhand der in der Klasse C gefundenen Annotationen angelegt.
Fall 2 ist am einfachsten zu handhaben. Mithilfe der Annotationen JPA geben wir die gewünschte Struktur der Tabelle T an. Fall 1 ist oft komplexer. Die Tabelle T wurde möglicherweise vor langer Zeit außerhalb jeglichen JPA-Kontexts erstellt. Ihre Struktur ist dann möglicherweise nicht optimal auf die Relational-Objekt-Brücke von JPA abgestimmt. Der Einfachheit halber gehen wir von Fall 2 aus, in dem die der Klasse C zugeordnete Tabelle T anhand der Annotationen JPA der Klasse C erstellt wird.
Erläutern wir die Annotationen JPA der Klasse [Personne]:
- Zeile 4: Die Annotation @Entity ist die erste unverzichtbare Annotation. Sie steht vor der Zeile, in der die Klasse deklariert wird, und gibt an, dass die betreffende Klasse von der Persistenzschicht JPA verwaltet werden soll. Ohne diese Annotation würden alle anderen Annotationen JPA ignoriert werden.
- Zeile 5: Die Annotation @Table bezeichnet die Datenbanktabelle, die durch die Klasse repräsentiert wird. Ihr Hauptargument ist „name“, das den Namen der Tabelle angibt. Fehlt dieses Argument, erhält die Tabelle den Namen der Klasse, in diesem Fall [Personne]. In unserem Beispiel ist die Annotation @Table daher überflüssig.
- Zeile 8: Die Annotation @Id dient dazu, das Feld in der Klasse zu bezeichnen, das den Primärschlüssel der Tabelle abbildet. Diese Annotation ist obligatorisch. Sie gibt hier an, dass das Feld id in Zeile 11 den Primärschlüssel der Tabelle abbildet.
- Zeile 9: Die Annotation @Column dient dazu, eine Verbindung zwischen einem Feld der Klasse und der Spalte der Tabelle herzustellen, deren Primärschlüssel das Feld abbildet. Das Attribut „name“ gibt den Namen der Spalte in der Tabelle an. Fehlt dieses Attribut, trägt die Spalte denselben Namen wie das Feld. In unserem Beispiel war das Argument „name“ daher nicht obligatorisch. Das Argument „nullable=false“ gibt an, dass die dem Feld zugeordnete Spalte nicht den Wert „NULL“ annehmen darf und das Feld daher zwingend einen Wert haben muss.
- Zeile 10: Die Anmerkung @GeneratedValue gibt an, wie der Primärschlüssel generiert wird, wenn er automatisch durch SGBD generiert wird. Dies wird in allen unseren Beispielen der Fall sein. Dies ist nicht zwingend erforderlich. So könnte unsere Person beispielsweise eine Matrikelnummer haben, die als Primärschlüssel dient und nicht durch SGBD generiert, sondern von der Anwendung festgelegt wird. In diesem Fall würde die Anmerkung @GeneratedValue fehlen. Das Argument „strategy“ gibt an, wie der Primärschlüssel generiert wird, wenn er durch das SGBD generiert wird. Nicht alle SGBD verwenden dieselbe Technik zur Generierung der Primärschlüsselwerte. Zum Beispiel:
verwendet einen Wertgenerator, der vor jedem Einfügevorgang aufgerufen wird | |
ist das Primärschlüsselfeld als Typ Identity definiert. Das Ergebnis ähnelt dem des Firebird-Wertgenerators, mit dem Unterschied, dass der Schlüsselwert erst nach dem Einfügen der Zeile bekannt ist. | |
verwendet ein Objekt namens SEQUENCE, das auch hier die Rolle eines Wertgenerators übernimmt |
Die Schicht JPA muss je nach SGBD unterschiedliche Befehle SQL generieren, um den Wertgenerator zu erstellen. Über die Konfiguration wird ihr der Typ des SGBD mitgeteilt, den sie verarbeiten soll. Dadurch kann sie erkennen, welche übliche Strategie zur Generierung von Primärschlüsselwerten für dieses SGBD gilt. Das Argument „strategy = GenerationType.AUTO“ weist die Ebene JPA an, diese übliche Strategie anzuwenden. Diese Technik hat in allen Beispielen dieses Dokuments für die sieben verwendeten SGBD funktioniert.
- Zeile 14: Die Anmerkung @Version bezeichnet das Feld, das zur Verwaltung gleichzeitiger Zugriffe auf dieselbe Zeile der Tabelle dient.
Um dieses Problem der gleichzeitigen Zugriffe auf dieselbe Zeile der Tabelle [personne] zu verstehen, nehmen wir an, dass eine Webanwendung die Aktualisierung einer Person ermöglicht, und betrachten wir den folgenden Fall:
Zum Zeitpunkt T1 ruft ein Benutzer U1 die Bearbeitungsfunktion für eine Person P auf. Zu diesem Zeitpunkt beträgt die Anzahl der Kinder 0. Er ändert diese Zahl auf 1, doch bevor er seine Änderung bestätigt, ruft ein Benutzer mit der ID U2 die Bearbeitungsseite für dieselbe Person P auf. Da U1 seine Änderung noch nicht bestätigt hat, sieht U2 auf seinem Bildschirm die Anzahl der Kinder als 0 angezeigt. U2 schreibt den Namen der Person P in Großbuchstaben um. Anschließend bestätigen U1 und U2 ihre Änderungen in dieser Reihenfolge. Die Änderung von U2 wird übernommen: In der Datenbank wird der Name in Großbuchstaben umgewandelt und die Anzahl der Kinder bleibt bei Null, obwohl U1 glaubt, sie auf 1 geändert zu haben.
Das Konzept der Personenversion hilft uns, dieses Problem zu lösen. Nehmen wir denselben Anwendungsfall:
Zum Zeitpunkt T1 ruft ein Benutzer U1 die Bearbeitungsfunktion für eine Person P auf. Zu diesem Zeitpunkt beträgt die Anzahl der Kinder 0 und die Version lautet V1. Er ändert die Anzahl der Kinder auf 1, doch bevor er seine Änderung bestätigt, ruft ein Benutzer mit der ID U2 den Bearbeitungsmodus für dieselbe Person P auf. Da U1 seine Änderung noch nicht bestätigt hat, sieht U2 die Anzahl der Kinder auf 0 und die Version auf V1. U2 ändert den Namen der Person P in Großbuchstaben. Anschließend bestätigen U1 und U2 ihre Änderungen in dieser Reihenfolge. Vor der Bestätigung einer Änderung wird überprüft, ob derjenige, der eine Person P ändert, dieselbe Version besitzt wie die aktuell gespeicherte Person P. Dies ist beim Benutzer U1 der Fall. Seine Änderung wird daher akzeptiert, und die Version der geänderten Person wird von V1 auf V2 geändert, um zu vermerken, dass die Person eine Änderung erfahren hat. Bei der Bestätigung der Änderung von U2 wird festgestellt, dass U2 eine Version V1 der Person P enthält, während deren aktuelle Version V2 lautet. Man kann dem Benutzer U2 dann mitteilen, dass jemand vor ihm tätig war und er von der neuen Version der Person P ausgehen muss. Er wird dies tun, eine Person P der Version V2 abrufen, die nun ein Kind hat, den Namen in Großbuchstaben umwandeln und die Änderung bestätigen. Seine Änderung wird akzeptiert, wenn die gespeicherte Person P noch immer die Version V2 hat. Letztendlich werden die von U1 und U2 vorgenommenen Änderungen berücksichtigt, während im Anwendungsfall ohne Versionsnummer eine der Änderungen verloren gegangen wäre.
Die Schicht [DAO] der Client-Anwendung kann die Version der Klasse [Personne] selbst verwalten. Bei jeder Änderung eines P-Objekts wird die Version dieses Objekts in der Tabelle um 1 erhöht. Die Annotation @Version ermöglicht es, diese Verwaltung an die Schicht JPA zu übertragen. Das betreffende Feld muss keineswegs wie im Beispiel version heißen. Es kann einen beliebigen Namen tragen.
Die Felder, die den Annotationen @Id und @Version entsprechen, sind Felder, die aufgrund der Persistenz vorhanden sind. Man bräuchte sie nicht, wenn die Klasse [Personne] nicht persistiert werden müsste. Man sieht also, dass ein Objekt je nachdem, ob es persistiert werden muss oder nicht, unterschiedlich dargestellt wird.
- Zeile 17: Erneut die Annotation @Column, um Informationen über die Spalte der Tabelle [personne] zu liefern, die dem Feld nom der Klasse Personne zugeordnet ist. Hier finden sich zwei neue Argumente:
- unique=true gibt an, dass der Name einer Person eindeutig sein muss. Dies führt in der Datenbank dazu, dass eine Eindeutigkeitsbeschränkung für die Spalte NOM der Tabelle [personne] hinzugefügt wird.
- length=30 legt die Anzahl der Zeichen in der Spalte NOM auf 30 fest. Das bedeutet, dass der Typ dieser Spalte VARCHAR(30) lautet.
- Zeile 24: Die Anmerkung @Temporal dient dazu, anzugeben, welcher Typ SQL einer Spalte/einem Feld vom Typ Datum/Uhrzeit zugewiesen werden soll. Der Typ TemporalType.DATE bezeichnet ein Datum ohne zugehörige Uhrzeit. Weitere mögliche Typen sind TemporalType.TIME zur Kodierung einer Uhrzeit und TemporalType.TIMESTAMP zur Kodierung eines Datums mit Uhrzeit.
Lassen Sie uns nun den Rest des Codes der Klasse [Personne] kommentieren:
- Zeile 6: Die Klasse implementiert die Schnittstelle Serializable. Die sérialisation eines Objekts besteht darin, es in eine Bitfolge umzuwandeln. Die désérialisation ist der umgekehrte Vorgang. Die Serialisierung/Deserialisierung wird insbesondere in Client-Server-Anwendungen verwendet, bei denen Objekte über das Netzwerk ausgetauscht werden. Client- oder Server-Anwendungen sind sich dieses Vorgangs nicht bewusst, da er von den JVM transparent durchgeführt wird. Damit dies möglich ist, müssen die Klassen der ausgetauschten Objekte jedoch mit dem Schlüsselwort Serializable „markiert“ sein.
- Zeile 37: ein Konstruktor der Klasse. Es ist zu beachten, dass die Felder „id“ und „version“ nicht zu den Parametern gehören. Diese beiden Felder werden nämlich von der JPA-Schicht und nicht von der Anwendung verwaltet.
- Zeilen 51 ff.: Die get- und set-Methoden für jedes der Felder der Klasse. Es ist zu beachten, dass die Annotationen JPA auf die get-Methoden der Felder gesetzt werden können, anstatt auf die Felder selbst. Die Position der Annotationen gibt an, welchen Modus JPA für den Zugriff auf die Felder verwenden soll:
- Wenn die Annotationen auf Feldebene gesetzt sind, greift JPA direkt auf die Felder zu, um sie zu lesen oder zu schreiben
- Wenn die Annotationen auf der Ebene der get-Methoden gesetzt sind, greift JPA über die get-/set-Methoden auf die Felder zu, um sie zu lesen oder zu schreiben
Die Position der Annotation @Id bestimmt die Position der Annotationen JPA in einer Klasse. Befindet sie sich auf Feldebene, bedeutet dies einen direkten Zugriff auf die Felder; befindet sie sich auf der Ebene von get, bedeutet dies einen Zugriff auf die Felder über die Methoden get und set. Die anderen Annotationen müssen dann auf die gleiche Weise wie die Annotation @Id platziert werden.
4.2.2. Konfiguration der Schicht JPA
Die Tests der Schicht JPA können mit der folgenden Architektur durchgeführt werden:
![]() |
- in [7]: die Datenbank, die anhand der Annotationen der Entität [Personne] sowie zusätzlicher Konfigurationen in einer Datei namens [persistence.xml] generiert wird
- in [5, 6]: eine von Hibernate implementierte Schicht JPA
- in [4]: die Entität [Personne]
- in [3]: ein Testprogramm im Konsolenmodus
Die Konfiguration der Schicht JPA erfolgt über die Datei [META-INF/persistence.xml]:
![]() |
Bei der Ausführung wird in der Datei Classpath der Anwendung nach der Datei [META-INF/persistence.xml] gesucht.
Sehen wir uns die Konfiguration der Schicht JPA an, die in der Datei [persistence.xml] unseres Projekts vorgenommen wurde:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">
<!-- Provider -->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<!-- Persistente Klassen -->
<property name="hibernate.archive.autodetection" value="class, hbm" />
<!-- Protokolle SQL
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="use_sql_comments" value="true"/>
-->
<!-- Verbindung JDBC -->
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa" />
<property name="hibernate.connection.username" value="jpa" />
<property name="hibernate.connection.password" value="jpa" />
<!-- Automatische Schemaerstellung -->
<property name="hibernate.hbm2ddl.auto" value="create" />
<!-- Dialekt -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<!-- Eigenschaften DataSource c3p0 -->
<property name="hibernate.c3p0.min_size" value="5" />
<property name="hibernate.c3p0.max_size" value="20" />
<property name="hibernate.c3p0.timeout" value="300" />
<property name="hibernate.c3p0.max_statements" value="50" />
<property name="hibernate.c3p0.idle_test_period" value="3000" />
</properties>
</persistence-unit>
</persistence>
Um diese Konfiguration zu verstehen, müssen wir uns noch einmal die Architektur des Datenzugriffs unserer Anwendung ansehen:
![]() |
- Die Datei [persistence.xml] konfiguriert die Schichten [4, 5, 6]
- [4]: Hibernate-Implementierung von JPA
- [5]: Hibernate greift über einen Verbindungspool auf die Datenbank zu. Ein Verbindungspool ist ein Vorrat an offenen Verbindungen mit dem SGBD. Auf ein SGBD greifen mehrere Benutzer zu, während es aus Leistungsgründen eine maximale Anzahl N gleichzeitig geöffneter Verbindungen nicht überschreiten darf. Gut geschriebener Code öffnet eine Verbindung zum SGBD nur für eine minimale Zeit: Er sendet Befehle an den SQL und schließt die Verbindung wieder. Dies wiederholt er jedes Mal, wenn er mit der Datenbank arbeiten muss. Der Aufwand für das Öffnen und Schließen einer Verbindung ist nicht zu vernachlässigen, und genau hier kommt der Verbindungspool ins Spiel. Dieser öffnet beim Start der Anwendung N1 Verbindungen zum SGBD. Der Pool stellt der Anwendung bei Bedarf eine offene Verbindung zur Verfügung. Diese wird an den Pool zurückgegeben, sobald die Anwendung sie nicht mehr benötigt – vorzugsweise so schnell wie möglich. Die Verbindung wird nicht geschlossen und steht dem nächsten Benutzer zur Verfügung. Ein Verbindungspool ist also ein System zur gemeinsamen Nutzung offener Verbindungen.
- [6]: Der Treiber JDBC des verwendeten SGBD
Sehen wir uns nun an, wie die Datei [persistence.xml] die oben genannten Ebenen [4, 5, 6] konfiguriert:
- Zeile 2: Das Stamm-Tag der Datei XML lautet <persistence>.
- Zeile 3: <persistence-unit> dient zur Definition einer Persistenz-Einheit. Es kann mehrere Persistenz-Einheiten geben. Jede von ihnen hat einen Namen (Attribut „name“) und einen Transaktionstyp (Attribut „transaction-type“). Die Anwendung greift über den Namen der Persistenz-Einheit auf diese zu, hier „jpa“. Der Transaktionstyp RESOURCE_LOCAL gibt an, dass die Anwendung die Transaktionen mit dem SGBD selbst verwaltet. Dies ist hier der Fall. Wenn die Anwendung in einem EJB3-Container ausgeführt wird, kann sie dessen Transaktionsdienst nutzen. In diesem Fall wird „transaction-type=JTA“ (Java-Transaktion API) angegeben. JTA ist der Standardwert, wenn das Attribut „transaction-type“ fehlt.
- Zeile 5: Das <provider>-Tag dient dazu, eine Klasse zu definieren, die die Schnittstelle [javax.persistence.spi.PersistenceProvider] implementiert. Diese Schnittstelle ermöglicht es der Anwendung, die Persistenzschicht zu initialisieren. Da wir eine JPA-/Hibernate-Implementierung verwenden, handelt es sich bei der hier verwendeten Klasse um eine Hibernate-Klasse.
- Zeile 6: Das Tag <properties> führt Eigenschaften ein, die für das jeweils ausgewählte provider spezifisch sind. Je nachdem, ob man sich für Hibernate, Toplink, Kodo usw. entschieden hat, gibt es also unterschiedliche Eigenschaften. Die folgenden Eigenschaften sind spezifisch für Hibernate.
- Zeile 8: Weist Hibernate an, das classpath des Projekts zu durchsuchen, um die Klassen mit der Annotation @Entity zu finden und diese zu verwalten. @Entity-Klassen können auch mit den Tags <class>nom_de_la_classe</class> direkt unter dem Tag <persistence-unit> deklariert werden. Genau das werden wir mit dem provider JPA / Toplink tun.
- Die Zeilen 10–12, die hier auskommentiert sind, konfigurieren die Konsolenprotokolle von Hibernate:
- Zeile 10: Legt fest, ob die von Hibernate ausgegebenen Befehle auf dem SQL angezeigt werden sollen oder nicht. Dies ist während der Einarbeitungsphase sehr hilfreich. Aufgrund der Relational-Objekt-Brücke arbeitet die Anwendung mit persistenten Objekten, auf die sie Operationen vom Typ [persist, merge, remove] anwendet. Es ist sehr interessant zu wissen, welche SQL-Befehle bei diesen Operationen tatsächlich ausgegeben werden. Wenn man diese untersucht, kann man nach und nach erahnen, welche Befehle vom Typ SQL Hibernate generieren wird, wenn man eine bestimmte Operation an den persistenten Objekten durchführt, und die Relational-Objekt-Brücke beginnt, im Kopf Gestalt anzunehmen.
- Zeile 11: Die auf der Konsole angezeigten Befehle SQL können übersichtlich formatiert werden, um das Lesen zu erleichtern
- Zeile 12: Die angezeigten Befehle SQL werden zudem mit Kommentaren versehen
- Die Zeilen 15–19 definieren die Schicht JDBC (Schicht [6] in der Architektur):
- Zeile 15: Die Treiberklasse JDBC des SGBD, hier MySQL5
- Zeile 16: Die URL der verwendeten Datenbank
- Zeilen 17, 18: der Benutzer der Verbindung und sein Passwort
- Zeile 22: Hibernate muss den SGBD kennen, mit dem es gerade arbeitet. Tatsächlich haben alle SGBD proprietäre Erweiterungen vom Typ SQL – eine elegante Methode zur automatischen Generierung von Primärschlüsselwerten –, … weshalb Hibernate den SGBD kennen muss, mit dem es arbeitet, um ihm die Befehle SQL zu senden, die dieser versteht. [MySQL5InnoDBDialect] bezeichnet das SGBD MySQL5 mit Tabellen vom Typ InnoDB, die Transaktionen unterstützen.
- Die Zeilen 24–28 konfigurieren den Verbindungspool c3p0 (Schicht [5] in der Architektur):
- Zeilen 24, 25: die minimale (Standardwert 3) und maximale Anzahl von Verbindungen (Standardwert 15) im Pool. Die anfängliche Anzahl von Verbindungen beträgt standardmäßig 3.
- Zeile 26: Maximale Wartezeit in Millisekunden auf eine Verbindungsanfrage des Clients. Nach Ablauf dieser Zeit sendet c3p0 eine Ausnahme zurück.
- Zeile 27: Für den Zugriff auf den Befehl BD verwendet Hibernate vorbereitete Befehle vom Typ SQL (PreparedStatement), die c3p0 im Cache speichern kann. Das bedeutet: Wenn die Anwendung ein zweites Mal einen vorbereiteten Befehl SQL anfordert, der bereits im Cache vorhanden ist, muss dieser nicht erneut vorbereitet werden (die Vorbereitung eines Befehls SQL ist mit Kosten verbunden), und stattdessen wird der im Cache befindliche Befehl verwendet. Hier wird die maximale Anzahl vorbereiteter SQL-Befehle angegeben, die der Cache über alle Verbindungen hinweg enthalten kann (ein vorbereiteter SQL-Befehl gehört zu einer Verbindung).
- Zeile 28: Häufigkeit der Überprüfung der Gültigkeit der Verbindungen in Millisekunden. Eine Verbindung aus dem Pool kann aus verschiedenen Gründen ungültig werden (der Treiber JDBC erklärt die Verbindung für ungültig, weil sie zu lange besteht, der Treiber JDBC weist „Fehler“ auf, ...).
- Zeile 20: Hier wird festgelegt, dass bei der Initialisierung der Persistenz-Einheit die Datenbankstruktur der @Entity-Objekte generiert wird. Hibernate verfügt nun über alle Werkzeuge, um die Befehle zur Generierung der Datenbanktabellen auszuführen:
- Anhand der Konfiguration der @Entity-Objekte kann Hibernate ermitteln, welche Tabellen generiert werden müssen
- die Zeilen 15–18 und 24–28 ermöglichen es ihm, eine Verbindung zum SGBD herzustellen
- Zeile 22 gibt an, welcher Dialekt SQL zur Generierung der Tabellen verwendet werden soll
Somit erstellt die hier verwendete Datei „[persistence.xml]“ bei jeder neuen Ausführung der Anwendung eine neue Datenbank. Die Tabellen werden neu angelegt („create table“), nachdem sie – sofern sie bereits existierten – gelöscht wurden („drop table“). Es sei darauf hingewiesen, dass dies natürlich nicht in einer Produktionsdatenbank durchgeführt werden sollte...
4.2.3. Beispiel 2: Eins-zu-Viele-Beziehung
4.2.3.1. Das Schema der Datenbank „ “
1 ![]() | 2 |
- in der Datenbank [1] und in [2], dessen DDL (MySQL5)
Ein Artikel A(id, version, name) gehört genau zu einer Kategorie C(id, version, name). Eine Kategorie C kann 0, 1 oder mehrere Artikel enthalten. Es besteht eine Eins-zu-Viele-Beziehung (Kategorie -> Artikel) und die umgekehrte Viele-zu-Eins-Beziehung (Artikel -> Kategorie). Diese Beziehung wird durch den Fremdschlüssel der Tabelle [article] auf die Tabelle [categorie] dargestellt (Zeilen 24–28 der Tabelle DDL).
4.2.3.2. Die @Entity-Objekte, die die Datenbank repräsentieren
Ein Artikel wird durch die folgende @Entity [Article] dargestellt:
package entites;
...
@Entity
@Table(name="jpa05_hb_article")
public class Article implements Serializable {
// Felder
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@SuppressWarnings("unused")
@Version
private int version;
@Column(length = 30)
private String nom;
// Hauptbeziehung „Artikel“ (many) -> „Kategorie“ (one)
// implementiert durch einen Fremdschlüssel (categorie_id) in „Artikel“
// 1 Artikel hat zwingend 1 Kategorie (nullable=false)
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "categorie_id", nullable = false)
private Categorie categorie;
// Konstruktoren
public Article() {
}
// Getter und Setter
...
// toString
public String toString() {
return String.format("Article[%d,%d,%s,%d]", id, version, nom, categorie.getId());
}
}
- Zeilen 9–11: Primärschlüssel der @Entity
- Zeilen 13–15: Versionsnummer
- Zeilen 17–18: Name des Artikels
- Zeilen 20–25: Eine Mehr-zu-Eins-Beziehung, die die @Entity Article mit der @Entity Categorie verknüpft:
- Zeile 23: die Annotation ManyToOne. Das „Many“ bezieht sich auf die @Entity Article, in der wir uns befinden, und das „One“ auf die @Entity Categorie (Zeile 25). Eine Kategorie (One) kann mehrere Artikel (Many) haben.
- Zeile 24: Die Annotation ManyToOne definiert die Fremdschlüsselspalte in der Tabelle [article]. Sie heißt (name) categorie_id, und jede Zeile muss einen Wert in dieser Spalte (nullable=false) enthalten.
- Zeile 25: Die Kategorie, zu der der Artikel gehört. Wenn ein Artikel in den Persistenzkontext aufgenommen wird, wird festgelegt, dass seine Kategorie nicht sofort dort abgelegt wird (fetch=FetchType.LAZY, Zeile 23). Es ist unklar, ob diese Anforderung sinnvoll ist. Das wird sich zeigen.
Eine Kategorie wird durch die folgende @Entity [Categorie] dargestellt:
package entites;
...
@Entity
@Table(name="jpa05_hb_categorie")
public class Categorie implements Serializable {
// Felder
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@SuppressWarnings("unused")
@Version
private int version;
@Column(length = 30)
private String nom;
// umgekehrte Beziehung „Kategorie (one)“ -> „Artikel (many)“ der Beziehung „Artikel (many)“ -> „Kategorie (one)“
// Kaskadierung beim Einfügen: Kategorie -> Einfügen von Artikeln
// Kaskadierung bei Aktualisierung: Kategorie -> Aktualisierung der Artikel
// Kaskadierung beim Löschen: Kategorie -> Artikel löschen
@OneToMany(mappedBy = "categorie", cascade = { CascadeType.ALL })
private Set<Article> articles = new HashSet<Article>();
// Konstruktoren
public Categorie() {
}
// Getter und Setter
...
// toString
public String toString() {
return String.format("Categorie[%d,%d,%s]", id, version, nom);
}
// bidirektionale Verknüpfung Kategorie <--> Artikel
public void addArticle(Article article) {
// Der Artikel wird zur Artikelsammlung der Kategorie hinzugefügt
articles.add(article);
// Der Artikel wechselt die Kategorie
article.setCategorie(this);
}
}
- Zeilen 8–11: der Primärschlüssel der @Entity
- Zeilen 12–14: ihre Version
- Zeilen 16–17: der Name der Kategorie
- Zeilen 19–24: die Menge (Set) der Artikel der Kategorie
- Zeile 23: Die Annotation @OneToMany bezeichnet eine Eins-zu-Viele-Beziehung. „One“ bezeichnet die @Entity [Categorie], in der wir uns befinden, „Many“ den Typ [Article] aus Zeile 24: Eine (One) Kategorie hat mehrere (Many) Artikel.
- Zeile 23: Die Annotation ist das Gegenteil (mappedBy) der Annotation ManyToOne, die auf dem Feld categorie der @Entity Article gesetzt ist: mappedBy=categorie. Die Beziehung ManyToOne, die auf das Feld categorie der @Entity Article angewendet wird, ist die Hauptbeziehung. Sie ist unverzichtbar. Sie verkörpert die Fremdschlüsselbeziehung, die die @Entity Article mit der @Entity Categorie verbindet. Die Beziehung OneToMany, die auf das Feld articles der @Entity Categorie angewendet wird, ist die umgekehrte Beziehung. Sie ist nicht zwingend erforderlich. Sie dient der Vereinfachung beim Abrufen der Artikel einer Kategorie. Ohne diese Vereinfachung würden diese Artikel über eine Abfrage JPQL abgerufen.
- Zeile 23: cascadeType.ALL sorgt dafür, dass die auf einer @Entity Categorie durchgeführten Operationen (persist, merge, remove) auf deren Artikel kaskadiert werden.
- Zeile 24: Die Artikel einer Kategorie werden in ein Objekt vom Typ Set<Article> abgelegt. Der Typ Set lässt keine Duplikate zu. Daher kann derselbe Artikel nicht zweimal in das Objekt Set<Article> aufgenommen werden. Was bedeutet „derselbe Artikel“? Um auszudrücken, dass der Artikel a derselbe ist wie der Artikel b, verwendet Java den Ausdruck a.equals(b). In der Klasse „Object“, der Basisklasse aller Klassen, ist a.equals(b) wahr, wenn a == b ist, also c.a.d. wenn die Objekte a und b denselben Speicherort haben. Man könnte sagen wollen, dass die Artikel a und b identisch sind, wenn sie denselben Namen haben. In diesem Fall muss der Entwickler zwei Methoden in der Klasse [Article] neu definieren:
- equals: muss „wahr“ zurückgeben, wenn beide Elemente denselben Namen haben
- hashCode: Muss für zwei Objekte [Article], die von der Methode equals als gleich angesehen werden, einen identischen ganzzahligen Wert zurückgeben. Hier wird der Wert also aus dem Namen des Artikels gebildet. Der von hashCode zurückgegebene Wert kann eine beliebige Ganzzahl sein. Er wird in verschiedenen Objektcontainern verwendet, insbesondere in Hashtabellen (Hashtable).
Die Relation OneToMany kann andere Typen als Set verwenden, um das „Many“ zu speichern, beispielsweise List-Objekte. Auf diese Fälle wird in diesem Dokument nicht eingegangen. Der Leser findet sie in [ref1].
- Zeile 38: Mit der Methode [addArticle] können wir einen Artikel zu einer Kategorie hinzufügen. Die Methode sorgt dafür, dass beide Enden der Beziehung OneToMany, die [Categorie] mit [Article] verbindet, aktualisiert werden.
4.3. API der Schicht JPA
Lassen Sie uns die Ausführungsumgebung eines JPA-Clients näher erläutern:
![]() |
Wir wissen, dass die Schicht JPA [2] eine Objekt-Relational-Brücke [3] / [4] erstellt. Als „Persistenzkontext“ bezeichnet man die Gesamtheit der Objekte, die von der Schicht JPA im Rahmen dieser Objekt-Relational-Brücke verwaltet werden. Um auf die Daten des Persistenzkontexts zuzugreifen, muss ein Client JPA [1] die Schicht JPA [2] durchlaufen:
- Er kann ein Objekt anlegen und die Schicht JPA auffordern, dieses persistent zu machen. Das Objekt ist dann Teil des Persistenzkontexts.
- Es kann von der Schicht [JPA] eine Referenz auf ein vorhandenes persistentes Objekt anfordern.
- Er kann ein persistentes Objekt ändern, das er von der Schicht JPA erhalten hat.
- Er kann die Schicht JPA auffordern, ein Objekt aus dem Persistenzkontext zu löschen.
Die Schicht JPA stellt dem Client eine Schnittstelle namens [EntityManager] zur Verfügung, die, wie der Name schon sagt, die Verwaltung der @Entity-Objekte des Persistenzkontexts ermöglicht. Im Folgenden stellen wir die wichtigsten Methoden dieser Schnittstelle vor:
fügt entity in den Persistenzkontext ein | |
entfernt entity aus dem Persistenzkontext | |
führt ein Objekt entity vom Client, das nicht vom Persistenzkontext verwaltet wird, mit dem Objekt entity aus dem Persistenzkontext zusammen, das denselben Primärschlüssel hat. Das Ergebnis ist das Objekt entity aus dem Persistenzkontext. | |
fügt ein in der Datenbank gesuchtes Objekt über dessen Primärschlüssel in den Persistenzkontext ein. Der Typ T des Objekts ermöglicht es der Schicht JPA zu erkennen, welche Tabelle abgefragt werden muss. Das so erstellte persistente Objekt wird an den Client zurückgegeben. | |
erstellt ein Query-Objekt aus einer Abfrage JPQL (Java Persistence Query Language). Eine Abfrage JPQL entspricht einer Abfrage SQL, sofern mit dem Unterschied, dass Objekte statt Tabellen abgefragt werden. | |
Methode, die der vorherigen ähnelt, mit dem Unterschied, dass queryText eine Befehl SQL und nicht JPQL. | |
Die Vorgehensweise ist identisch mit createQuery, mit dem Unterschied, dass der Befehl JPQL queryText in eine Konfigurationsdatei ausgelagert und mit einem Namen verknüpft wurde. Dieser Name ist der Parameter der Methode. |
Ein Objekt EntityManager hat einen Lebenszyklus, der nicht unbedingt dem der Anwendung entspricht. Es hat einen Anfang und ein Ende. So kann ein Client JPA nacheinander mit verschiedenen Objekten EntityManager arbeiten. Der einem EntityManager zugeordnete Persistenzkontext hat denselben Lebenszyklus wie dieses Objekt. Sie sind untrennbar miteinander verbunden. Wenn ein EntityManager-Objekt geschlossen wird, wird sein Persistenzkontext bei Bedarf mit der Datenbank synchronisiert und existiert anschließend nicht mehr. Um wieder über einen Persistenzkontext zu verfügen, muss ein neues EntityManager erstellt werden.
Der Client JPA kann mit dem folgenden Befehl ein EntityManager und damit einen Persistenzkontext erstellen:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("nom d'une unité de persistance");
- javax.persistence.Persistence ist eine statische Klasse, mit der eine Factory für Objekte vom Typ EntityManager abgerufen werden kann. Diese Factory ist mit einer bestimmten Persistenz-Einheit verknüpft. Wie wir uns erinnern, ermöglicht die Konfigurationsdatei [META-INF/persistence.xml] die Definition von Persistenzeinheiten, und diese haben einen Namen:
<persistence-unit name="elections-dao-jpa-mysql-01PU" transaction-type="RESOURCE_LOCAL">
Im obigen Beispiel heißt die Persistenz-Einheit elections-dao-jpa-mysql-01PU. Mit ihr geht eine eigene Konfiguration einher, insbesondere die SGBD, mit der sie zusammenarbeitet. Die Anweisung [Persistence.createEntityManagerFactory("elections-dao-jpa-mysql-01PU")] erstellt eine Objektfabrik vom Typ EntityManagerFactory, die Objekte vom Typ EntityManager bereitstellen kann, die zur Verwaltung von Persistenzkontexten dienen, die mit der Persistenzeinheit namens elections-dao-jpa-mysql-01PU. Das Abrufen eines Objekts vom Typ EntityManager und damit eines Persistenzkontexts erfolgt ausgehend vom Objekt EntityManagerFactory wie folgt:
Die folgenden Methoden der Schnittstelle [EntityManager] ermöglichen die Verwaltung des Lebenszyklus des Persistenzkontexts:
Der Persistenzkontext wird geschlossen. Erzwingt die Synchronisation des Persistenzkontexts mit der Datenbank:
| |
Der Persistenzkontext wird von allen Objekten geleert, aber nicht geschlossen. | |
Der Persistenzkontext wird wie für close() beschrieben mit der Datenbank synchronisiert. |
Der Client JPA kann die Synchronisierung des Persistenzkontexts mit der Datenbank mithilfe der Methode [EntityManager] erzwingen. flush (vorherige). Die Synchronisation kann explizit oder implizit erfolgen. Im ersten Fall ist es Aufgabe des Clients, Operationen mit flush durchzuführen, wenn er Synchronisationen vornehmen möchte; andernfalls erfolgt die Synchronisation zu bestimmten Zeitpunkten, die wir im Folgenden näher erläutern werden. Der Synchronisationsmodus wird durch die folgenden Methoden der Schnittstelle [EntityManager] verwaltet:
Für flushmode gibt es zwei mögliche Werte: FlushModeType.AUTO (Standard): Die Synchronisation erfolgt vor jeder Abfrage SELECT, die an die Datenbank gestellt wird. FlushModeType.COMMIT: Die Synchronisation erfolgt erst am Ende der Transaktionen in der Datenbank. | |
gibt den aktuellen Synchronisationsmodus an |
Fassen wir zusammen: Im Modus FlushModeType.AUTO, dem Standardmodus, wird der Persistenzkontext zu folgenden Zeitpunkten mit der Datenbank synchronisiert:
- vor jeder SELECT-Operation in der Datenbank
- am Ende einer Transaktion in der Datenbank
- nach einer flush- oder close-Operation am Persistenzkontext
Im Modus FlushModeType.COMMIT verhält es sich genauso, mit Ausnahme von Vorgang 1, der nicht stattfindet. Der normale Interaktionsmodus mit der Schicht JPA ist ein transaktionaler Modus. Der Client führt innerhalb einer Transaktion verschiedene Operationen auf dem Persistenzkontext durch. In diesem Fall entsprechen die Zeitpunkte der Synchronisation des Persistenzkontexts mit der Datenbank den oben genannten Fällen 1 und 2 im Modus AUTO sowie ausschließlich Fall 2 im Modus COMMIT.
Abschließend noch der Modus API der Query-Schnittstelle, über die Befehle vom Typ JPQL an den Persistenzkontext oder Befehle vom Typ SQL direkt an die Datenbank gesendet werden können, um dort Daten abzurufen. Die Query-Schnittstelle sieht wie folgt aus:
![]() |
- 1 – Die Methode getResultList führt einen Befehl SELECT aus, der mehrere Objekte zurückgibt. Diese werden in einem Objekt List bereitgestellt. Dieses Objekt ist eine Schnittstelle. Diese stellt ein Objekt Iterator bereit, mit dem die Elemente der Liste L in folgender Form durchlaufen werden können:
Iterator iterator = L.iterator();
while (iterator.hasNext()) {
// das Objekt iterator.next() auswerten, das das aktuelle Element der Liste darstellt
...
}
Die Liste L kann auch mit einem for ausgewertet werden:
for (Object o : L) {
// Objekt o auswerten
}
- 2 – Die Methode getSingleResult führt einen Befehl JPQL / SQL / SELECT aus, der ein einzelnes Objekt zurückgibt.
- 3 – Die Methode executeUpdate führt einen Befehl SQL (Update oder Delete) aus und gibt die Anzahl der von der Operation betroffenen Zeilen zurück.
- 4 – Die Methode setParameter(String, Object) ermöglicht es, einem benannten Parameter eines parametrisierten Befehls JPQL einen Wert zuzuweisen
- 5 – Die Methode setParameter(int, Object) verwendet jedoch nicht den Namen des Parameters, sondern dessen Position im Befehl JPQL.
4.4. Die Abfragen JPQL
JPQL (Java Persistence Query Language) ist die Abfragesprache der Schicht JPA. Die Sprache JPQL ist mit der Sprache SQL von Datenbanken verwandt. Während SQL mit Tabellen arbeitet, arbeitet JPQL mit den Bildobjekten dieser Tabellen. Wir werden ein Beispiel innerhalb der folgenden Architektur betrachten:
![]() |
Die Datenbank, die wir [dbrdvmedecins2] nennen werden, ist eine MySQL5-Datenbank mit vier Tabellen:
![]() |
Sie enthält Informationen zur Verwaltung der Termine einer Ärztegruppe.
4.4.1. Die Tabelle [MEDECINS]
Sie enthält Informationen zu den Ärzten.
![]() | ![]() |
- ID: Identifikationsnummer des Arztes – Primärschlüssel der Tabelle
- VERSION: Nummer zur Identifizierung der Version des Datensatzes in der Tabelle. Diese Nummer wird bei jeder Änderung des Datensatzes um 1 erhöht.
- NOM: Name des Arztes
- PRENOM: sein Vorname
- TITRE: sein/ihr Titel (Fräulein, Frau, Herr)
4.4.2. Die Tabelle [CLIENTS]
Die Patienten der verschiedenen Ärzte sind in der Tabelle [CLIENTS] gespeichert:
![]() | ![]() |
- ID: Nummer zur Identifizierung des Kunden – Primärschlüssel der Tabelle
- VERSION: Nummer zur Identifizierung der Zeilenversion in der Tabelle. Diese Nummer wird bei jeder Änderung an der Zeile um 1 erhöht.
- NOM: Name des Kunden
- PRENOM: sein Vorname
- TITRE: seine Anrede (Fräulein, Frau, Herr)
4.4.3. Die Tabelle [CRENEAUX]
Sie listet die Zeitfenster auf, in denen RV möglich sind:
![]() |
![]() |
- ID: Nummer zur Identifizierung des Zeitfensters – Primärschlüssel der Tabelle (Zeile 8)
- VERSION: Nummer zur Identifizierung der Version der Zeile in der Tabelle. Diese Nummer wird bei jeder Änderung an der Zeile um 1 erhöht.
- ID_MEDECIN: Nummer zur Identifizierung des Arztes, zu dem dieses Zeitfenster gehört – Fremdschlüssel auf die Spalte MEDECINS (ID).
- HDEBUT: Startzeit des Zeitfensters
- MDEBUT: Startminute des Zeitfensters
- HFIN: Endzeit des Zeitfensters
- MFIN: Minuten des Zeitfensterschlusses
Die zweite Zeile der Tabelle [CRENEAUX] (siehe [1] oben) gibt beispielsweise an, dass der Termin Nr. 2 um 8:20 Uhr beginnt und um 8:40 Uhr endet und der Ärztin Nr. 1 (Frau Marie PELISSIER).
4.4.4. Die Tabelle [RV]
Sie listet die für jeden Arzt vergebenen RV auf:
![]() |
- ID: Nummer, die den RV eindeutig identifiziert – Primärschlüssel
- JOUR: Tag des RV
- ID_CRENEAU: Zeitfenster des RV – Fremdschlüssel auf das Feld [ID] der Tabelle [CRENEAUX] – legt sowohl das Zeitfenster als auch den betreffenden Arzt fest.
- ID_CLIENT: Kundennummer, für die die Reservierung vorgenommen wird – Fremdschlüssel auf das Feld [ID] der Tabelle [CLIENTS]
Diese Tabelle verfügt über eine Eindeutigkeits für die Werte der verknüpften Spalten (JOUR, ID_CRENEAU):
Wenn eine Zeile der Tabelle [RV] den Wert (JOUR1, ID_CRENEAU1) für die Spalten (JOUR, ID_CRENEAU) aufweist, darf dieser Wert an keiner anderen Stelle vorkommen. Andernfalls würde dies bedeuten, dass zwei RV gleichzeitig für denselben Arzt erfasst wurden. Aus Sicht der Java-Programmierung löst der Treiber JDBC der Datenbank in diesem Fall einen SQLException aus.
Die Zeile mit dem Wert 3 für id (siehe [1] oben) bedeutet, dass am 23.08.2006 ein RV für den Terminblock Nr. 20 und den Kunden Nr. 4 gebucht wurde. Aus der Tabelle [CRENEAUX] geht hervor, dass der Termin Nr. 20 dem Zeitfenster 16:20 – 16:40 Uhr entspricht und der Ärztin Nr. 1 (Frau Marie PELISSIER) zugeordnet ist. Aus der Tabelle [CLIENTS] geht hervor, dass es sich bei Kunde Nr. 4 um Frau Brigitte BISTROU handelt.
4.4.5. Erstellung der Datenbank
Um die Tabellen anzulegen und zu füllen, kann das Skript [dbrdvmedecins2.sql] verwendet werden. Mit [WampServer] kann wie folgt vorgegangen werden:
![]() |
- In [1] klickt man auf das Symbol von [WampServer] und wählt die Option [PhpMyAdmin] [2] aus,
- bei [3] wählt man im sich öffnenden Fenster den Link [Bases de données] aus,
![]() |
- zu [2], man erstellt eine Datenbank, der man den Namen [4] und die Kodierung [5] gegeben hat,
- in [7] wurde die Datenbank erstellt. Man klickt auf den entsprechenden Link,
![]() |
- in [8], importiert man eine Datei SQL,
- die man im Dateisystem über die Schaltfläche [9] auswählt,
![]() |
- in [11] wählt man das Skript SQL aus und führt es in [12] aus,
- in [13] wurden die vier Tabellen der Datenbank angelegt. Man folgt einem der Links,
![]() |
- in [14], den Inhalt der Tabelle.
Im weiteren Verlauf werden wir nicht mehr auf diese Datenbank zurückkommen. Der Leser ist jedoch eingeladen, deren Entwicklung im Laufe der Programme zu verfolgen, insbesondere wenn es nicht funktioniert.
4.4.6. Die Ebene [JPA]
Kehren wir zur Architektur des Beispiels zurück:
![]() |
Wir erstellen nun das Maven-Projekt der Schicht [JPA].
4.4.7. Das NetBeans-Projekt
Sieht wie folgt aus:
![]() |
- In [1] erstellen wir ein Maven-Projekt vom Typ [Java Application] [2],
- in [3] wird dem Projekt ein Name gegeben,
![]() |
- in [4] wird das Projekt generiert.
4.4.8. Erstellung der Ebene [JPA]
Kommen wir zurück zu der Architektur, die wir erstellen müssen:
![]() |
Mit NetBeans lässt sich die Ebene [JPA] automatisch generieren. Es ist interessant, diese Methoden der automatischen Generierung zu kennen, da der generierte Code wertvolle Hinweise darauf gibt, wie JPA-Entitäten geschrieben werden sollten.
4.4.9. Einrichten einer NetBeans-Verbindung zur Datenbank
- Starten Sie SGBD MySQL 5, damit BD verfügbar ist,
- eine NetBeans-Verbindung zur Datenbank [dbrdvmedecins2] erstellen,
![]() |
- auf der Registerkarte „[Services] [1]“, im Zweig „[Databases] [2]“ den Treiber „JDBC MySQL [3]“ auswählen,
- und wählen Sie anschließend die Option [4] „Connect Using“ aus, um eine Verbindung zu einer Datenbank MySQL herzustellen,
- in [5] die erforderlichen Informationen eingeben. In [6] den Namen der Datenbank, in [7] den Datenbankbenutzer und dessen Passwort,
- in [8] können die eingegebenen Daten überprüft werden,
- in [9] die erwartete Meldung, wenn diese korrekt sind,
![]() |
- in [10] wird die Verbindung hergestellt. Dort sind die vier Tabellen der verbundenen Datenbank zu sehen.
4.4.10. Erstellung einer Persistenz-Einheit
Kehren wir zur Architektur zurück, die gerade aufgebaut wird:
![]() |
Wir sind gerade dabei, die Schicht [JPA] aufzubauen. Die Konfiguration dieser Schicht erfolgt in einer Datei namens [persistence.xml], in der Persistenz-Einheiten definiert werden. Jede dieser Einheiten benötigt die folgenden Informationen:
- die Zugangsdaten zur Datenbank (JDBC, URL, Benutzername, Passwort),
- die Klassen, die als Abbilder der Datenbanktabellen dienen,
- die verwendete Implementierung JPA. Tatsächlich handelt es sich bei JPA um eine Spezifikation, die von verschiedenen Produkten implementiert wird. Hier verwenden wir Hibernate.
NetBeans kann diese Persistenzdatei mithilfe eines Assistenten generieren.
![]() |
- Rechtsklick auf das Projekt und Auswahl der Option zum Erstellen einer Persistenzeinheit [1],
- in [2] eine Persistenz-Einheit erstellen,
![]() |
- in [3] der neu erstellten Persistenz-Einheit einen Namen geben,
- in [4] die Hibernate-Implementierung JPA (JPA 2.0) auswählen,
- in [5] angeben, dass die Tabellen aus BD bereits angelegt sind und daher nicht erneut angelegt werden. Den Assistenten bestätigen,
- in [6], das neue Projekt,
- in [7] wurde die Datei [persistence.xml] im Ordner [META-INF] generiert,
- in [8] wurden dem Maven-Projekt neue Abhängigkeiten hinzugefügt.
Die generierte Datei „[META-INF/persistence.xml]“ 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-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
<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>
Sie enthält die im Assistenten angegebenen Informationen:
- Zeile 3: den Namen der Persistenz-Einheit,
- Zeile 3: die Art der Transaktionen mit der Datenbank. Hier gibt „RESOURCE_LOCAL“ an, dass die Anwendung ihre Transaktionen selbst verwaltet,
- Zeilen 6–9: die Eigenschaften JDBC der Datenquelle.
Auf der Registerkarte „[Design]“ erhält man einen Überblick über die Datei „[persistence.xml]“:
![]() |
Um Hibernate-Protokolle zu erhalten, ergänzen wir die Datei [persistence.xml] 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-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dbrdvmedecins2"/>
<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.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
- Zeile 11: Hier werden die von Hibernate ausgegebenen Befehle SQL angefordert,
- Zeile 12: Diese Eigenschaft ermöglicht eine formatierte Anzeige dieser Befehle.
Dem Projekt wurden Abhängigkeiten hinzugefügt. Die Datei [pom.xml] lautet wie folgt:
<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-rdvmedecins-jpql-hibernate</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mv-rdvmedecins-jpql-hibernate</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.transaction</groupId>
<artifactId>jboss-transaction-api_1.1_spec</artifactId>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
<version>2.7.7</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.15.0-GA</version>
</dependency>
<dependency>
<groupId>org.hibernate.common</groupId>
<artifactId>hibernate-commons-annotations</artifactId>
<version>4.0.1.Final</version>
</dependency>
</dependencies>
</project>
Die hinzugefügten Abhängigkeiten betreffen alle Hibernate (ORM). Wir fügen die Abhängigkeit des Treibers JDBC von MySQL hinzu:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
4.4.11. Generierung der Entitäten JPA
Die Entitäten JPA können über einen NetBeans-Assistenten generiert werden:
![]() |
- In [1] werden Entitäten JPA aus einer Datenbank erstellt,
![]() |
- in [2] wählt man die zuvor erstellte Verbindung [dbrdvmedecins2] aus,
- in [3] wählt man alle Tabellen der zugehörigen Datenbank aus,
![]() |
- In [4] werden die den vier Tabellen zugeordneten Java-Klassen benannt,
- sowie einen Paketnamen: [5],
- in [6] fasst JPA Zeilen aus den Tabellen von BD in Sammlungen zusammen. Wir wählen die Liste als Sammlung aus,
![]() |
- in [7], die vom Assistenten erstellten Java-Klassen.
4.4.12. Die generierten Entitäten JPA
Die Entität [Medecin] ist das Abbild der Tabelle [medecins]. Die Java-Klasse ist mit Annotationen übersät, die den Code auf den ersten Blick schwer lesbar machen. Behält man nur das bei, was für das Verständnis der Rolle der Entität wesentlich ist, erhält man den folgenden Code:
package rdvmedecins.jpa;
...
@Entity
@Table(name = "medecins")
public class Medecin implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;
// Konstruktoren
....
// Getter und Setter
....
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- In Zeile 4 macht die Annotation @Entity die Klasse [Medecin] zu einer Entität JPA, c.a.d. eine Klasse, die über die Entitäten API und JPA mit der Tabelle von BD verknüpft ist,
- Zeile 5: Der Name der Tabelle BD, die der Entität JPA zugeordnet ist. Jedes Feld der Tabelle entspricht einem Feld in der Java-Klasse,
- Zeile 6: Die Klasse implementiert die Schnittstelle `Serializable`. Dies ist in Client-Server-Anwendungen erforderlich, in denen Entitäten zwischen Client und Server serialisiert werden.
- Zeilen 10–11: Das Feld „id“ der Klasse [Medecin] entspricht dem Feld [ID] (Zeile 10) der Tabelle [medecins],
- Zeilen 13–14: Das Feld „titel“ der Klasse [Medecin] entspricht dem Feld [TITRE] (Zeile 13) der Tabelle [medecins],
- Zeilen 16–17: Das Feld „name“ der Klasse [Medecin] entspricht dem Feld [NOM] (Zeile 16) der Tabelle [medecins],
- Zeilen 19–20: Das Feld „version“ der Klasse [Medecin] entspricht dem Feld [VERSION] (Zeile 19) der Tabelle [medecins]. Hier erkennt der Assistent nicht, dass es sich bei der Spalte tatsächlich um eine Versionsspalte handelt, die bei jeder Änderung der Zeile, zu der sie gehört, erhöht werden muss. Um ihr diese Rolle zuzuweisen, muss die Anmerkung @Version hinzugefügt werden. Dies werden wir in einem späteren Schritt tun,
- Zeilen 22–23: Das Feld „prenom“ der Klasse [Medecin] entspricht dem Feld [PRENOM] der Tabelle [medecins],
- Zeilen 10–11: Das Feld „id“ entspricht dem Primärschlüssel „[ID]“ der Tabelle. Die Annotationen in den Zeilen 8–9 verdeutlichen diesen Punkt,
- Zeile 8: Die Anmerkung @Id gibt an, dass das mit dieser Anmerkung versehene Feld dem Primärschlüssel der Tabelle zugeordnet ist,
- Zeile 9: Die Schicht [JPA] generiert den Primärschlüssel für die Zeilen, die sie in die Tabelle [Medecins] einfügt. Es gibt mehrere mögliche Strategien. Hier gibt die Strategie GenerationType.IDENTITY an, dass die Schicht JPA den Modus auto_increment der Tabelle MySQL verwenden wird,
- Zeilen 25–26: Die Tabelle [creneaux] verfügt über einen Fremdschlüssel auf die Tabelle [medecins]. Ein Termin gehört zu einem Arzt. Umgekehrt sind einem Arzt mehrere Termine zugeordnet. Es handelt sich also um eine 1:n-Beziehung (ein Arzt zu mehreren Zeitfenstern), die durch die Annotation @OneToMany durch JPA (Zeile 25) qualifiziert wird. Das Feld in Zeile 26 enthält alle Termine des Arztes. Dies geschieht ohne Programmierung. Um Zeile 25 vollständig zu verstehen, müssen wir die Klasse [Creneau] vorstellen.
Diese lautet wie folgt:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "MDEBUT")
private int mdebut;
@Column(name = "HFIN")
private int hfin;
@Column(name = "HDEBUT")
private int hdebut;
@Column(name = "MFIN")
private int mfin;
@Column(name = "VERSION")
private int version;
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin idMedecin;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idCreneau")
private List<Rv> rvList;
// Konstruktoren
...
// Getter und Setter
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
Wir gehen nur auf die neuen Anmerkungen ein:
- Wir haben bereits erwähnt, dass die Tabelle [creneaux] einen Fremdschlüssel zur Tabelle [medecins] besitzt: Ein Termin ist einem Arzt zugeordnet. Einem Arzt können mehrere Termine zugeordnet sein. Es besteht eine Beziehung von der Tabelle [creneaux] zur Tabelle [medecins], die als „mehrere (Zeitfenster) zu einem (Arzt)“ definiert ist. Die Anmerkung @ManyToOne in Zeile 32 dient dazu, den Fremdschlüssel zu kennzeichnen,
- Zeile 31 mit der Anmerkung @JoinColumn legt die Fremdschlüsselbeziehung fest: Die Spalte [ID_MEDECIN] der Tabelle [creneaux] ist ein Fremdschlüssel auf die Spalte [ID] der Tabelle [medecins],
- Zeile 33: ein Verweis auf den Arzt, dem der Terminblock gehört. Auch dies lässt sich ohne Programmierung ermitteln.
Die Fremdschlüsselbeziehung zwischen der Entität [Creneau] und der Entität [Medecin] wird somit durch zwei Anmerkungen dargestellt:
- in der Entität [Creneau]:
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin idMedecin;
- in der Entität [Medecin]:
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idMedecin")
private List<Creneau> creneauList;
Beide Annotationen spiegeln dieselbe Beziehung wider: die des Fremdschlüssels von der Tabelle [creneaux] zur Tabelle [medecins]. Man sagt, dass sie zueinander invers sind. Nur die Beziehung @ManyToOne ist unverzichtbar. Sie kennzeichnet die Fremdschlüsselbeziehung eindeutig. Die Beziehung @OneToMany ist optional. Ist sie vorhanden, verweist sie lediglich auf die Beziehung @ManyToOne, mit der sie verknüpft ist. Dies ist die Bedeutung des Attributs mappedBy in Zeile 1 der Entität [Medecin]. Der Wert dieses Attributs ist der Name des Feldes der Entität [Creneau], das die Annotation @ManyToOne trägt, welche den Fremdschlüssel angibt. Ebenfalls in dieser Zeile 1 der Entität [Medecin] legt das Attribut cascade=CascadeType.ALL das Verhalten der Entität [Medecin] gegenüber der Entität [Creneau] fest:
- Wenn eine neue Entität [Medecin] in die Datenbank eingefügt wird, müssen auch die Entitäten [Creneau] aus dem Feld in Zeile 2 eingefügt werden;
- wenn ein Datensatz [Medecin] in der Datenbank geändert wird, müssen auch die Datensätze [Creneau] im Feld der Zeile 2 geändert werden,
- wenn eine Entität [Medecin] aus der Datenbank gelöscht wird, müssen auch die Entitäten [Creneau] im Feld der Zeile 2 gelöscht werden.
Den Code der beiden anderen Entitäten geben wir ohne besondere Anmerkungen an, da sie keine neuen Notationen einführen.
Die Entität [Client]
package rdvmedecins.jpa;
...
@Entity
@Table(name = "clients")
public class Client implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "TITRE")
private String titre;
@Column(name = "NOM")
private String nom;
@Column(name = "VERSION")
private int version;
@Column(name = "PRENOM")
private String prenom;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "idClient")
private List<Rv> rvList;
// Konstruktoren
...
// Getter und Setter
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- Die Zeilen 24–25 spiegeln die Fremdschlüsselbeziehung zwischen der Tabelle [rv] und der Tabelle [clients] wider.
Die Entität [Rv]:
package rdvmedecins.jpa;
...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Column(name = "JOUR")
@Temporal(TemporalType.DATE)
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau idCreneau;
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Client idClient;
// Konstruktoren
...
// Getter und Setter
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
...
}
}
- Zeile 13 beschreibt das Feld „Tag“ vom Java-Typ Date. Es wird angegeben, dass in der Tabelle [rv] die Spalte [JOUR] (Zeile 12) vom Typ „Datum“ (ohne Uhrzeit) ist,
- Zeilen 16–18: definieren die Fremdschlüsselbeziehung der Tabelle [rv] zur Tabelle [creneaux],
- Zeilen 20–22: definieren die Fremdschlüsselbeziehung der Tabelle [rv] zur Tabelle [clients].
Die automatische Generierung der Entitäten JPA liefert uns eine Arbeitsgrundlage. Manchmal reicht diese aus, manchmal nicht. Das ist hier der Fall:
- muss die Annotation @Version zu den verschiedenen Versionsfeldern der Entitäten hinzugefügt werden,
- es müssen Methoden toString geschrieben werden, die aussagekräftiger sind als die generierten,
- die Entitäten [Medecin] und [Client] sind analog. Wir werden sie von einer Klasse [Personne] ableiten,
- und die inversen Beziehungen @OneToMany der Beziehungen @ManyToOne entfernen. Sie sind nicht unbedingt erforderlich und führen zu Komplikationen bei der Programmierung;
- wir entfernen die Validierung @NotNull für die Primärschlüssel. Wenn wir eine Entität JPA zusammen mit MySQL persistieren, hat die ursprüngliche Entität einen Primärschlüssel null. Erst nach der Speicherung in der Datenbank erhält der Primärschlüssel des gespeicherten Elements einen Wert.
Mit diesen Spezifikationen ergeben sich folgende Klassen:
Die Klasse „Person“ wird verwendet, um Ärzte und Kunden darzustellen:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@MappedSuperclass
public class Personne implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "TITRE")
private String titre;
@Basic(optional = false)
@Column(name = "NOM")
private String nom;
@Basic(optional = false)
@Column(name = "VERSION")
@Version
private int version;
@Basic(optional = false)
@Column(name = "PRENOM")
private String prenom;
// Konstruktoren
...
// Getter und Setter
...
@Override
public String toString() {
return String.format("[%s,%s,%s,%s,%s]", id, version, titre, prenom, nom);
}
}
- Zeile 6: Es ist zu beachten, dass die Klasse [Personne] selbst keine Entität (@Entity) ist. Sie wird die übergeordnete Klasse für Entitäten sein. Die Annotation @MappedSuperClass kennzeichnet diese Situation.
Die Entität [Client] kapselt die Zeilen der Tabelle [clients]. Sie leitet sich von der vorherigen Klasse [Personne] ab:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "clients")
public class Client extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
// Konstruktoren
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Client[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
}
}
- Zeile 6: Die Klasse [Client] ist eine JPA-Entität,
- Zeile 7: Sie ist der Tabelle [clients] zugeordnet,
- Zeile 8: Sie leitet sich von der Klasse [Personne] ab.
Die Entität [Medecin], die die Zeilen der Tabelle [medecins] kapselt, folgt demselben Muster:
package rdvmedecins.jpa;
import java.io.Serializable;
import javax.persistence.*;
@Entity
@Table(name = "medecins")
public class Medecin extends Personne implements Serializable {
private static final long serialVersionUID = 1L;
// Konstruktoren
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Médecin[%s,%s,%s,%s]", getId(), getTitre(), getPrenom(), getNom());
}
}
Die Entität [Creneau] kapselt die Zeilen der Tabelle [creneaux]:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.List;
import javax.persistence.*;
@Entity
@Table(name = "creneaux")
public class Creneau implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "MDEBUT")
private int mdebut;
@Basic(optional = false)
@Column(name = "HFIN")
private int hfin;
@Basic(optional = false)
@NotNull
@Column(name = "HDEBUT")
private int hdebut;
@Basic(optional = false)
@Column(name = "MFIN")
private int mfin;
@Basic(optional = false)
@Column(name = "VERSION")
@Version
private int version;
@JoinColumn(name = "ID_MEDECIN", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Medecin medecin;
// Konstruktoren
...
// Getter und Setter
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
// TODO: Warnung – diese Methode funktioniert nicht, wenn die ID-Felder nicht gesetzt sind
...
}
@Override
public String toString() {
return String.format("Creneau [%s, %s, %s:%s, %s:%s,%s]", id, version, hdebut, mdebut, hfin, mfin, medecin);
}
}
- Die Zeilen 40–42 modellieren die „Viele-zu-Eins“-Beziehung, die zwischen der Tabelle [creneaux] und der Tabelle [medecins] der Datenbank besteht: Ein Arzt hat mehrere Termine, ein Termin gehört zu einem einzigen Arzt.
Die Entität [Rv] kapselt die Zeilen der Tabelle [rv]:
package rdvmedecins.jpa;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.*;
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ID")
private Long id;
@Basic(optional = false)
@Column(name = "JOUR")
@Temporal(TemporalType.DATE)
private Date jour;
@JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Creneau creneau;
@JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
@ManyToOne(optional = false)
private Client client;
// Konstruktoren
...
// Getter und Setter
...
@Override
public int hashCode() {
...
}
@Override
public boolean equals(Object object) {
...
}
@Override
public String toString() {
return String.format("Rv[%s, %s, %s]", id, creneau, client);
}
}
- Die Zeilen 27–29 modellieren die „Viele-zu-Eins“-Beziehung, die zwischen der Tabelle [rv] und der Tabelle [clients] (ein Kunde kann in mehreren Rv vorkommen) der Datenbank, und die Zeilen 23–25 die „Viele-zu-Eins“-Beziehung, die zwischen der Tabelle [rv] und der Tabelle [creneaux] besteht (ein Termin kann in mehreren Rv vorkommen).
4.4.13. Der Code für den Datenzugriff
Wir fügen nun dem Projekt den Code für den Datenzugriff über die Schicht JPA hinzu:
![]() |
![]() |
Die Klasse [MainJpql] sieht wie folgt aus:
package rdvmedecins.console;
import java.util.Scanner;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class MainJpql {
public static void main(String[] args) {
// EntityManagerFactory
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mv-rdvmedecins-jpql-hibernatePU");
// entityManager
EntityManager em = emf.createEntityManager();
// Tastatur-Scanner
Scanner clavier = new Scanner(System.in);
// Abfrage-Eingabeschleife JPQL
System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
String requete = clavier.nextLine();
while (!requete.trim().equals("*")) {
try {
// Anzeige des Abfrageergebnisses
for (Object o : em.createQuery(requete).getResultList()) {
System.out.println(o);
}
} catch (Exception e) {
System.out.println("L'exception suivante s'est produite : " + e);
}
// Leeren des Persistenzkontexts
em.clear();
// neue Abfrage
System.out.println("---------------------------------------------");
System.out.println("Requete JPQL sur la base dbrdvmedecins2 (* pour arrêter) :");
requete = clavier.nextLine();
}
// Schließen der Ressourcen
em.close();
emf.close();
}
}
- Zeile 12: Erstellung der Klasse EntityManagerFactory, die mit der zuvor erstellten Persistenz-Einheit verknüpft ist. Der Parameter der Methode createEntityManagerFactory ist der Name dieser Persistenz-Einheit:
<persistence-unit name="mv-rdvmedecins-jpql-hibernatePU" transaction-type="RESOURCE_LOCAL">
...
</persistence-unit>
- Zeile 14: Erstellung von EntityManager, das die Persistenzschicht verwaltet,
- Zeile 19: Eingabe einer Abfrage JPQL select,
- Zeilen 23–28: Anzeige des Abfrageergebnisses,
- Zeile 20: Die Eingabe wird beendet, wenn der Benutzer * eingibt.
Frage: Geben Sie die Abfragen JPQL an, mit denen sich folgende Informationen abrufen lassen:
- Liste der Ärzte in absteigender Reihenfolge ihrer Namen
- Liste der Ärzte, deren Titel = 'Mr' ist
- Liste der Terminfenster von Frau Pelissier
- Liste der vereinbarten Termine in aufsteigender Reihenfolge nach Tagen
- Liste der Kunden (Nachname), die am 24.08.2006 einen Termin bei Frau PELISSIER vereinbart haben
- Anzahl der Kunden von Frau PELISSIER am 24.08.2006
- Kunden, die keinen Termin vereinbart haben
- Ärzte, die keinen Termin haben
Man orientiert sich am Beispiel aus Abschnitt 2.7 von [ref1]. Hier ein Ausführungsbeispiel:
- Zeile 2: die Abfrage JPQL,
- Zeilen 3–11: die entsprechende Abfrage SQL,
- Zeilen 12–15: das Ergebnis der Abfrage JPQL.
4.5. Zusammenhänge zwischen Persistenzkontext und SGBD
4.5.1. Die Klasse „Person“
4.5.2. Das Testprogramm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | |
4.5.3. Die Hibernate-Konfiguration
4.5.4. Die Konfiguration von log4j.properties
4.5.5. Die Ergebnisse
Frage: Stellen Sie einen Zusammenhang zwischen dem Java-Code und den angezeigten Ergebnissen her.





































