Inhaltsverzeichnis

Das klappt ja schon ganz gut mit Java EE und MVC, wir haben sogar schon eigene Webseiten mit Datenbank im Hintergrund geschrieben. Die schönsten Sachen haben wir aber noch gar nicht gesehen. Hier lernst du nun, deine eigenen Persistence Entities und Enterprise Java Beans zu schreiben. đŸ–‹ïž

Lernziele dieser Einheit

Nach Abschluss dieser Einheit kannst du 


🏁

Allgemeines zu Datenbanken in Java

Bildnachweis: Pixabay: rawpixel

Wie Java mit der Datenbank spricht

O/R-Mapping in Java
đŸ›ąïž JDBC: Allgemeine API fĂŒr den Zugriff auf relationale Datenbanken
đŸ›ąïž JPA: Auf JDBC aufbauende API fĂŒr O/R-Mapper, die Javaobjekte in TabelleneintrĂ€ge und umgekehrt umwandeln

Von der Idee zur fertigen Anwendung

Die untenstehende Zeichnung zeigt die wesentlichen Schritte, die bei der Entwicklung einer Java-EE-Anwendung anfallen. Die wichtigsten davon schauen wir uns anhand eines kleinen Fallbeispiels auf den folgenden Reitern etwas genauer an.

Schritte wÀhrend der Entwicklung

ZunÀchst brauchen wir ein gutes Datenmodell, das nicht zu kompliziert aber dennoch flexibel ist. Unsere kleine Anwendung modelliert eine Buchsammlung. Wir haben daher EntitÀten wie Buch, Autor oder Verlag, die in Beziehung zueinander stehen. ZusÀtzlich hat jeder Autor und jeder Verlag eine Adresse.

Datenmodell mit den EntitÀten Buch, Autor, Verlag und Adresse

In Javacode sieht das ganze ungefĂ€hr so aus. Die Details dazu schauen wir uns natĂŒrlich noch an.

@Entity public class Buch implements Serializable { @Id private String isbn = ""; private String titel = ""; private String untertitel = ""; private BuchKategorie kategorie = BuchKategorie.UNBEKANNT; private int anzahlSeiten = 0; @ManyToMany List<Autor> autoren = new ArrayList<>(); @ManyToOne private Verlag verlag = null; // Setter und Getter 
 } public enum BuchKategorie { UNBEKANNT, BELLETRISTIK, FACHBUCH, SAMMELBAND, ZEITSCHRIFT; } @Entity public class Autor implements Serializable { @Id @GeneratedValue private long id; private String vorname; private String nachname; private String titel; @OneToOne Adresse adresse = null; @ManyToMany(mappedBy="autoren") List<Buch> buecher = new ArrayList<>(); // Setter und Getter 
 } @Entity public class Verlag implements Serializable { @Id @GeneratedValue private long id; private String name; private String rechtsform; @OneToOne Adresse adresse = null; @OneToMany(mappedBy="verlag") List<Buch> buecher = new ArrayList<>(); // Setter und Getter 
 } @Entity public class Adresse implements Serializable { @Id @GeneratedValue private long id; private String strasse = ""; private String hausnummer = ""; private String postleitzahl = ""; private String ort = ""; private String land = ""; // Setter und Getter 
 }

Darauf aufbauend können wir nun Services definieren, die sinnvolle Funktionen rund um die EntitĂ€ten zur VerfĂŒgung stellen. Im einfachsten Fall bieten wir hierfĂŒr die ĂŒblichen CRUD-Operationen: Create, Read, Update und Delete.

Klassendiagramm der Klassen BuchService, AutorService, VerlagService @Stateless public class BuchService { @PersistenceContext EntityManager em; // BĂŒcher selektieren public Buch findByIsbn(String isbn) { 
 } public List<Buch> findByAutor(Autor autor) { 
 } public List<Buch> findByVerlag(Verlag verlag) { 
 } public List<Buch> findByTitleContains(String title) { 
 } // Neue BĂŒcher speichern public Buch saveNew(Buch buch) { 
 } // BĂŒcher Verlage Ă€ndern public Buch update(Buch buch) { 
 } public void delete(Buch buch) { 
 } } @Stateless public class VerlagService { 
 } @Stateless public class AutorService { 
 }

Und last but not least eine schöne BenutzeroberflÀche, entweder als serverseitige MVC-Webanwendung, als clientseitige Webanwendung oder als Native Client. Oder jede beliebige Kombination daraus, ganz wie du willst.

BenutzeroberflĂ€chen fĂŒr Smartphone, Tablet, Desktop und Notebook @WebServlet(urlPatterns={"/start/"}) public class StartPageServlet extends HttpServlet { @EJB BuchService buchService; @EJB AutorService autorService; @EJB VerlagService verlagService; @Override public void doGet(HttpServletRequest request, HttpServletResponse) throws ServletException, IOException { 
 } }

Bildnachweise: Pixabay: Nick_H, Pixabay: JuralMin

Welche Datenbank nutzen wir eigentlich?

Netbeans enthÀlt die Datenbank Apache Derby. SelbstverstÀndlich können wir auch jede andere Datenbank verwenden, wenn wir sie selbst konfigurieren. Aber wozu? Derby funktioniert prima.

Netbeans mit gestarteter Derby-Datenbank

Über die Datenbankwerkzeuge in Netbeans

Bildnachweis fĂŒr das Endesymbol: Pixabay: janf93

Aufgabe 1: Ein kleines Datenbank-Quiz

Aufgabe 1.1: Datenbankzugriffe mit Java

a) Welche beiden Datenbank-APIs kennt die Java Enterprise Edition?

  1. JPA und JDBB
  2. JDK und JDBC
  3. JFK und JDCB
  4. JPA und JDBC

b) Welche Funktion erfĂŒllen die „Java Database Connection”-Klassen?

  1. Sie legen passende Tabellen fĂŒr die Persistence Entities an.
  2. Sie dienen als Low-Level API dazu, SQL-Befehle an die Datenbank zu schicken.
  3. Sie sprechen einen O/R-Mapper an, der TabelleneintrÀge in Javaobjekte umwandeln kann.
  4. Sie dienen der Nutzung von Persistence Entities innerhalb der EJBs.

c) Und welche Funktion erfĂŒllt die „Java Persistence API”?

  1. Sie legt passende Tabellen fĂŒr die Persistence Entities an.
  2. Sie dient als Low-Level API dazu, SQL-Befehle an die Datenbank zu schicken.
  3. Sie spricht einen O/R-Mapper an, der TabelleneintrÀge in Javaobjekte umwandeln kann.
  4. Sie dient der Nutzung von Persistence Entities innerhalb der EJBs.

Aufgabe 1.2: Vorgehen bei der Entwicklung

a) In welcher Reihenfolge sind die folgenden Schritte wĂ€hrend der Entwicklung durchzufĂŒhren?

  1. Enterprise Java Beans entwickeln
  2. BenutzeroberflÀche oder Clients entwickeln
  3. Datenmodell definieren
  4. Tabellen anlegen (falls von Hand erledigt)
  5. Serviceschicht definieren
  6. Persistence Entities anlegen
  7. Webservices anlegen

b) Entfernte Clients können die Persistence Entities direkt nutzen, um Daten zu lesen oder zu schreiben?

  1. Wahr
  2. Falsch

c) Welche Methoden sollten die Enterprise Java Beans in aller Regel beinhalten?

  1. Create, Read, Unlock, Delete
  2. Create, Rest, Update, Dismiss
  3. Create, Read, Update, Delete
  4. Create, Read, Update, Discard

Aufgabe 1.3: Datenbanken in Netbeans

a) Welche Datenbanken werden von Netbeans unterstĂŒzt?

  1. Apache Derby
  2. PostgreSQL, mySQL und MariaDB
  3. Alle Datenbanken, fĂŒr die es JDBC-Treiber gibt

b) Welche Datenbank liefert Netbeans praktischerweise schon mit aus?

  1. Apache Derby
  2. MariaDB
  3. Microsoft SQL Server
  4. Oracle Express Edition
  5. Oracle mySQL
  6. PostgreSQL
  7. SQLite
  8. Keine

c) Welche der folgenden Datenbankoperationen können mit Netbeans ausgefĂŒhrt werden?

  1. Tabellen anlegen
  2. Tabellen bearbeiten
  3. Tabellen löschen
  4. Tabelleninhalte anzeigen
  5. Tabelleninhalte einfĂŒgen
  6. Tabelleninhalte Àndern
  7. Tabelleninhalte löschen
  8. SQL-Befehle abschicken

Lösung:
Aufgabe 1.1: 4, 2, 3
Aufgabe 1.2: 3 → 4 → 6 → 5 → 1 → 7 → 2, 2, 3
Aufgabe 1.3: 3, 1, alle

Definition der Persistence Entities

Bildnachweis: Pixabay: rawpixel

Anatomie einer Persistence Entity

Persistence Entities sind einfache Beans, die mit @Entity gekennzeichnet werden. Außerdem wird empfohlen, dass sie das Serializable-Interface implementieren.

@Entity public class Autor implements Serializable {

HĂ€ufig besitzen sie als einzigen SchlĂŒsselwert ein Feld namens id, das automatisch mit einer fortlaufenden Nummer versorgt wird. Dies wird durch die beiden Annotationen @Id und @GeneratedValue ausgedrĂŒckt.

@Id @GeneratedValue private long id = 0;

Alle weiteren Felder sind automatisch NichtschlĂŒsselfelder. Sie benötigen im einfachsten Fall keine Annotationen, können aber welche besitzen, um die Feldeigenschaften genauer zu spezifizieren. Das schauen wir uns auf Folie 11 nochmal an. Die Felder sollten private oder protected sein.

private String vorname = ""; private String nachname = ""; private String titel = "";

Jede Persistence Entity sollte mindestens einen öffentlichen, parameterlosen Konstruktor besitzen, in dem alle Felder mit leeren Werten vorbelegt werden. In diesem Beispiel ist der Konstruktor leer, da den Feldern bereits bei ihrer Deklaration ein Wert zugewiesen wird.

public Autor() { }

DarĂŒber hinaus können beliebige weitere Konstruktoren definiert werden, um die Anlage neuer Objekte zu vereinfachen. Vorgaben gibt es hierfĂŒr keine, oftmals hat man aber noch einen Konstruktor, der fĂŒr jedes Feld einen Parameter besitzt.

public Autor(String vorname, String nachname, String titel) { this.vorname = vorname; this.nachname = nachname; this.titel = titel; }

Und damit es eine echte Bean ist, muss es zu jedem Feld mindestens einen Getter geben. In der Regel gibt es je Feld auch einen Setter, bei berechneten Feldern, deren Wert nicht in der Datenbank gespeichert wird, kann er aber auch ausbleiben.

// // Normale Felder mit Setter und Getter // public void setId(long id) { this.id = id; } public long getId() { return this.id; } public void setVorname(String vorname) { this.vorname = vorname; } public String getVorname() { return this.vorname; } public void setNachname(String nachname) { this.nachname = nachname; } public String getNachname() { return this.nachname; } public void setTitel(String titel) { this.titel = titel; } public String getTitel() { return this.titel; } // // Berechnete Felder ohne Setter // public String getNameKomplett() { String name = this.titel + " " + this.vorname + " " + this.nachname; return name.trim(); }
// // SchlĂŒssel // @Id @GeneratedValue private long id = 0; // // Felder // private String vorname = ""; private String nachname = ""; private String titel = ""; // // Konstruktoren // public Autor() { } public Autor(String vorname, String nachname, String titel) { this.vorname = vorname; this.nachname = nachname; this.titel = titel; } // // Normale Felder mit Setter und Getter // public void setId(long id) { this.id = id; } public long getId() { return this.id; } public void setVorname(String vorname) { this.vorname = vorname; } public String getVorname() { return this.vorname; } public void setNachname(String nachname) { this.nachname = nachname; } public String getNachname() { return this.nachname; } public void setTitel(String titel) { this.titel = titel; } public String getTitel() { return this.titel; } // // Berechnete Felder ohne Setter // public String getNameKomplett() { String name = this.titel + " " + this.vorname + " " + this.nachname; return name.trim(); }
}

Darf's ein bisschen mehr sein?

đŸȘ Ohne weitere Angaben, darf eine Persistence Entity nur ein SchlĂŒsselfeld besitzen:
@Entity public class Verlag { @Id @GeneratedValue private long id = 0; 
 }
đŸȘ Mehrere SchlĂŒsselwerte mĂŒssen in einer eigenen Klasse definiert werden.
đŸȘ Die Klasse muss mit @IdClass der Entity zugeordnet werden.
public class BuchId { private String isbn = ""; private int auflage = 0; // Konstruktoren 
 // Setter und Getter 
 } @Entity @IdClass(BuchId.class) public class Buch { @Id private String isbn = ""; @Id private int auflage = 0; // Der ganze Rest 
 }

Definition der Tabellen- und Feldeigenschaften

Benötigte Annotationen

📎 @Table Steht vor der Klasse und definiert weitere Tabelleneigenschaften.
📎 @Index Definiert die Felder eines Index auf die Tabelle.
📎 @Column Steht vor einem Feld und definiert weitere Feldeigenschaften.
📎 @Lob Kennzeichnet ein Feld als Large Object mit unbegrenzter LĂ€nge.

Praktisches Beispiel

@Entity @Table( name = "T_BUCH", schema = "BUCHSAMMLUNG", indexes = { @Index( name = "I_AUTOR_JAHR", columnList = "autor, jahr DESC" ), @Index( name = "I_JAHR", columnList = "jahr DESC" ) } ) public class Buch implements Serializable { @Id @Column(length=30) private String isbn = ""; @Column(nullable=false, length=50) private String autor; @Column(nullable=false, precision=4) private long jahr; @Column(nullable=false, precision=7, scale=2) private long preis; @Lob private String klappentext = ""; // Und so weiter 
 }

FremdschlĂŒsselbeziehungen richtig abbilden

🔑 FremdschlĂŒssel werden als Objektreferenzen in Java abgebildet.
🔑 Eine Beziehung kann dabei die KardinalitĂ€ten 1:1, 1:n, n:1 oder n:m besitzen.
âžĄïž Unidirektionale Beziehungen verweisen einfach von einer Klasse auf eine andere.
↔ Bei bidirektionalen Beziehungen verweisen beide Klassen gegenseitig aufeinander.
KardinalitĂ€t Einfache VerknĂŒpfung Optionale RĂŒckverknĂŒpfung
1:1 @OneToOne @OneToOne(mappedBy="feldname")
1:n @OneToMany geht nicht, siehe Anmerkungen im Reiter
n:1 @ManyToOne @OneToMany(mappedBy="feldname")
n:m @ManyToMany @ManyToMany(mappedBy="feldname")

Beispiel

@Entity public class Buch { 
 @ManyToOne( // Viele BĂŒcher haben einen Verlag optional = false, // Es muss immer einen Verlag geben fetch = FetchType.LAZY // Daten erst bei einem Zugriff darauf laden ) Verlag verlag = null; @ManyToOne( optional = false, fetch = FetchType.EAGER // Daten so frĂŒh wie möglich laden ) Autor autor = null; }

Unidirektionale VerknĂŒpfung

Eine Entity kann mit @OneToOne auf eine beliebige andere Entity verweisen, um einen FremdschlĂŒssel mit 1:1-KardinalitĂ€t abzubilden. In der Tabelle entsteht dadurch ein FremdschlĂŒsselfeld.

@Entity public class Autor { 
 @OneToOne Adresse adresse = null; } @Entity public class Adresse { @Id @GeneratedValue private long id = 0; 
 }

Bidirektionale VerknĂŒpfung

Eine bidirektionale VerknĂŒpfung entsteht, indem zusĂ€tzlich zur VerknĂŒpfung von der einen Klasse auf die andere die zweite Klasse mit @OneToOne(mappedBy="
") auf das Feld der ersten Klasse zurĂŒckverweist. In der Datenbank entsteht dadurch kein zweiter FremdschlĂŒssel.

@Entity public class Autor { 
 @OneToOne Adresse adresse = null; } @Entity public class Adresse { 
 @OneToOne(mappedBy="adresse") Autor autor = null; }

Unidirektionale VerknĂŒpfung

1:n-Beziehungen funktionieren grundsĂ€tzlich nicht anders als 1:1-Beziehungen. Doch anstelle einer einfachen Referenz gibt es nun eine ganze Liste, auf die eine Entity verweisen kann. In der Datenbank wird dies durch ein FremdschlĂŒsselfeld in der Zieltabelle abgebildet.

@Entity public class Buch { 
 @OneToMany List<Bewertung> bewertungen = new ArrayList<>(); } @Entity public class Bewertung { 
 }

Bidirektionale VerknĂŒpfung

Hier mĂŒssen wir leider gleich auf eine kleine SchrĂ€gheit in der Java Persistence API hinweisen. Normalerweise wĂŒrde man erwarten, dass sich mit @ManyToOne(mappedBy="
") auch eine rĂŒckwĂ€rtsgerichtete Beziehung herstellen lĂ€sst, wie im folgenden Beispiel:

@Entity public class Buch { 
 @OneToMany List<Bewertung> bewertungen = new ArrayList<>(); } @Entity public class Bewertung { 
 @ManyToOne(mappedBy="bewertungen") Buch buch = null; }

Das funktioniert aber nicht, da @ManyToOne die einzige Annotation ist, die das Attribut mappedBy nicht kennt. Stattdessen muss die Beziehung als bidirektionale n:1-Beziehung abgebildet werden:

@Entity public class Buch { 
 @OneToMany(mappedBy="buch") List<Bewertung> bewertungen = new ArrayList<>(); } @Entity public class Bewertung { 
 @ManyToOne Buch buch = null; }

Unidirektionale VerknĂŒpfung

n:1 ist das Gegenteil von 1:n, logisch. Technisch gesehen ist es aber mehr mit 1:1 verwandt, da auch hier einfach ein FremdschlĂŒsselfeld in der Quelltabelle entsteht. Allerdings ist es hier erlaubt, dass mehrere DatensĂ€tze auf dieselbe ZielentitĂ€t verweisen.

@Entity public class Buch { 
 @ManyToOne Verlag verlag = null: } @Entity public class Verlag { 
 }

Bidirektionale VerknĂŒpfung

Hier gibt es wieder die Möglichkeit, mit @OneToMany(mappedBy="
") eine inverse VerknĂŒpfung zu definieren. Die Annotation muss dabei vor einer Liste stehen.

@Entity public class Buch { 
 @ManyToOne Verlag verlag = null: } @Entity public class Verlag { 
 @OneToMany(mappedBy="verlag") List<Buch> buecher = new ArrayList<>(); }

Unidirektionale VerknĂŒpfung

Nach all den vorherigen Beispielen, wie wird da wohl eine n:m-Beziehung aussehen? Richtig, wir verwenden @ManyToMany vor einer Liste. In der Datenbank entsteht dadurch eine VerknĂŒpfungstabelle.

@Entity public class Buch { 
 @ManyToMany List<Autor> autoren = new ArrayList<>(); } @Entity public class Autor { 
 }

Bidirektionale VerknĂŒpfung

Und wie immer auch mit RĂŒckfahrticket, in diesem Fall @ManyToMany(mappendBy="
") ebenfalls vor einer Liste.

@Entity public class Buch { 
 @ManyToMany List<Autor> autoren = new ArrayList<>(); } @Entity public class Autor { 
 @ManyToMany(mappedBy="autoren") List<Buch> buecher = new ArrayList<>(); }

Aufgabe 2: Wir eröffnen ein Kino

Hier ist es: Unser Datenmodell in seiner vollen Pracht. 🌅 Um die Aufgabe etwas leichter zu machen, haben wir den Beziehungen eine Richtung gegeben und kleine Bezeichnungen hinzugefĂŒgt. Schaue dir das Modell in aller Ruhe an und versuche, so viel wie möglich nachzuvollziehen. đŸ€”
Falls du dich wunderst, warum es eine Tabelle mit Standorten gibt: Der ehemalige Besitzer der Schauburg, Georg Fricker, war bis zum Schluss auch GeschĂ€ftsfĂŒhrer vom Filmpalast am ZKM.

Komplettes E/R-Diagramm unseres Kinobeispiels

Als kleine Anschubfinanzierung haben wir den oberen Teil rund um die Standorte des Kinos đŸŽžïž bereits in Java umgesetzt. Unten findest du den Quellcode.

EntitĂ€ten Standort, Mitarbeiter, Adresse und Öffnungszeit @Entity public class Standort implements Serializable { @Id @GeneratedValue private long id = 0; private String name = ""; @OneToMany(mappedBy="standort") List<Oeffnungszeit> oeffnungszeiten = new ArrayList<>(); @OneToOne Adresse adresse = new Adresse(); @OneToMany(mappedBy="standort") List<Mitarbeiter> mitarbeiter = new ArrayList<>(); @OneToMany(mappedBy="standort") List<Spielplan> = spielplaene new ArrayList<>(); // Konstruktoren 
 // Setter und Getter 
 } @Entity public class Oeffnungszeit implements Serializable { @Id @GeneratedValue private long id = 0; private Wochentag wochentag = Wochentag.UNBEKANNT; private Time vonUhrzeit = new Time(); private Time bisUhrzeit = new Time(); @ManyToOne Standort standort = new Standort(); // Konstrukturen 
 // Setter und Getter 
 } public enum Wochentag { UNBEKANNT, MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, FREITAG, SAMSTAG, SONNTAG; } @Entity public class Adresse implements Serializable { @Id @GeneratedValue private long id = 0; private String strasse = ""; private String hausnummer = ""; private String postleitzahl = ""; private String ort = ""; private String land = ""; // Konstruktoren // Setter und Getter } @Entity public class Mitarbeiter implements Serializable { @Id @GeneratedValue private long id = 0; private String vorname = ""; private String nachname = ""; private Geschlecht geschlecht = Geschlecht.UNBEKANNT; private int durchwahl = 0; private String email = ""; @Column(precision=7, scale=2) private long monatsgehalt = 1000.00; @OneToOne Adresse adresse = new Addresse(); @ManyToOne Standort standort = new Standort(); // Konstruktoren 
 // Setter und Getter 
 } public enum GESCHLECHT { UNBEKANNT, MAENNLICH, WEIBLICH; }

Jetzt bist du dran. đŸ‘‰đŸœ ErgĂ€nze den Javacode um die fehlenden EntitĂ€ten rund im den Spielplan. Die Beziehungen zwischen den Klassen sollen bidirektional umgesetzt werden und Filmgenres können sein: Action, Drama, Fantasy, SciFi, Komödie oder Animation.

EntitĂ€ten Spieplan, VorfĂŒhrung, Film und Mitwirkender

Hier die Musterlösung fĂŒr die Persistence Entities rund um den Spielplan:

@Entity public class Spielplan implements Serializable { @Id @GeneratedValue private long id = 0; private String name = ""; @Lob private String webseitentext = ""; @ManyToOne Standort standort = new Standort(); @OneToMany(mappedBy="spielplan") List<Voerfuerhung> vorfuehrungen = new ArrayList<>(); // Konstruktoren // Setter und Getter 
 } @Entity public class Vorfuehrung implements Serializable { @Id @GeneratedValue private long id = 0; private Wochentag wochentag = Wochentag.UNBEKANNT; private Time vonUhrzeit = new Time(); private Time bisUhrzeit = new Time(); private String kinosaal = ""; @ManyToOne Spielplan spielplan = new Spielplan(); @ManyToOne Film film = new Film(); // Konstruktoren 
 // Setter und Getter 
 } @Entity public class Film implements Serializable { @Id @GeneratedValue private long id = 0; private String name = ""; private int jahr = 0; private Genre genre = Genre.UNBEKANNT; private String verleiher = ""; @Lob private String webseitentext = ""; @OneToMany(mappedBy="film") List<Vorfuehrung> vorfuehrungen = new ArrayList<>(); @ManyToMany List<Mitwirkender> mitwirkende = new ArrayList<>(); // Konstruktoren 
 // Setter und Getter 
 } public enum Genre { UNBEKANNT, ACTION, DRAMA, FANTASY, SCIFI, COMEDY, ANIMATION; } @Entity public class Mitwirkender implements Serializable { @Id @GeneratedValue private long id = 0; private String vorname = ""; private String nachname = ""; private String artDerMitwirkung = ""; private String nameImFilm = ""; @ManyToMany(mappedBy="mitwirkende") Lilst<Film> filme = new ArrayList<>(); // Konstruktoren 
 // Setter und Getter 
 }

Aufgabe 3: Ein kleines Persistence-Entity-Quiz

Aufgabe 3.1: SchlĂŒsselwerte definieren

a) Persistence Entities besitzen immer nur ein SchlĂŒsselfeld.

  1. Wahr
  2. Falsch

b) Persistence Entities können auch nicht-automatisch vergebene SchlĂŒsselfelder besitzen.

  1. Wahr
  2. Falsch

c) Welche Annotation kennzeichnet die SchlĂŒsselfelder einer Persistence Entity?

  1. @Key
  2. @Id
  3. @IdClass
  4. @IdField

d) Wie muss man vorgehen, um eine Entity mit mehr als einem SchlĂŒsselfeld zu definieren?

  1. Das geht nicht, siehe Frage a).
  2. Einfach alle Felder mit @IdField kennzeichnen.
  3. Alle Felder mit @Id kennzeichnen und mit @IdClass auf eine Klasse mit nur den SchlĂŒsselfeldern verweisen.
  4. Alle SchlĂŒsselfelder in einer eigenen Klasse definieren und dann eine Referenz auf die Klasse mit @IdClass kennzeichnen.

Aufgabe 3.2: Tabellen- und Feldeigenschaften

a) Wie oft kann die Annotation @Table in einer Persistence Entity verwendet werden?

  1. Gar nicht, es handelt sich um eine Annotation fĂŒr EJBs
  2. Höchstens einmal
  3. Genau einmal
  4. Höchstens drei mal
  5. Beliebig oft

b) An welcher Stelle muss die Annotation @Column in einer Persistence Entity stehen?

  1. Irgendwo vor der Klasse
  2. Immer vor der Annotation @Entity
  3. Immer nach der Annotation @Entity
  4. In jeder beliebigen Stelle innerhalb der Klasse
  5. Vor beliebig vielen Attributen der Klasse
  6. Vor beliebig vielen Methodenparametern

c) Wozu dienen die Indizes, die mit @Table definiert werden können?

  1. Der Beschleunigung hÀufig benötigter Datenselektionen
  2. Der Beschleunigung hĂ€ufig benötigter EinfĂŒgeoperationen
  3. Der Vorsortierung hÀufig geschriebener DatensÀtze
  4. Der KompatibilitÀt mit anderen O/R-Mappern

d) Was drĂŒcken die Werte precision und scale der Annotation @Column aus?

  1. Die Mindest- und MaximallÀnge zeichenartiger Felder
  2. Wie genau eine FremdschlĂŒsselbeziehung spezifiziert ist
  3. Die maximale Anzahl Ziffern einer Kommazahl sowie wie viele davon Nachkommastellen sind
  4. Die Anzahl, wie oft ein Boolean auf false umschalten darf
  5. Die Genauigkeit, mit der du diese Antworten liest
  6. Das maximale Gesamtgewicht aller Entities in Kilogramm oder Pfund

e) Wie kann der Name einer Tabelle oder eines Felds in der Datenbank ĂŒberschrieben werden?

  1. Mit der Annotation @Name vor der Klasse oder dem Feld
  2. Mit dem Attribut name der Annotationen @Table und @Column
  3. Mit der Annotation @BeanName innerhalb der rufenden EJBs

f) Welche Vor- und Nachteile ergeben sich aus der Annotation @Lob?

  1. Die dadurch gekennzeichneten Stringfelder können jede beliebige GrĂ¶ĂŸe annehmen, drĂŒcken dafĂŒr aber die Performance nach unten.
  2. Die dadurch gekennzeichneten Integerfelder können beliebig große Zahlen annehmen, dafĂŒr kann nach ihnen nicht selektiert werden.
  3. Man lobt den Entwickler fĂŒr seine gute Arbeit, es gibt aber keine Annotation fĂŒr @Tadel.

Aufgabe 3.3: FremdschlĂŒsselbeziehungen

a) Welche der folgenden Beziehungstypen können nicht mit JPA abgebildet werden?

  1. 1:1-Beziehungen
  2. 1:n-Beziehungen
  3. n:1-Beziehungen
  4. n:m-Beziehungen

b) Wie werden 16:9-Beziehungen mit der Java Persistence API abgebildet?

  1. Gar nicht, du warst wohl zu oft im Kino!
  2. Mit der Annotation @Widescreen
  3. Mit den Annotationen @HdReady und @FullHD

c) Was sind bidirektionale Beziehungen?

  1. Einfache Beziehungen von einer EntitÀt zu einer anderen
  2. Beziehungen mit mehr als einer ZielentitÀt
  3. Beziehungen in beide Richtungen zwischen zwei EntitÀten
  4. Beziehungen mit mehr als einer StartidentitÀt

d) Welche Anpassung muss bei einer unidirektionalen Beziehung an der ZielentitÀt vorgenommen werden?

  1. Keine, das ist nur bei bidirektionalen Beziehungen erforderlich.
  2. Es muss eine passende Annotation mit der Eigenschaft mappedBy hinzugefĂŒgt werden.
  3. Es muss eine gegenlĂ€ufige Annotation ohne besondere Eigenschaften hinzugefĂŒgt werden.
  4. Es muss mit @ForeignKey ein rĂŒckwĂ€rtsgerichteter FremdschlĂŒssel hinzugefĂŒgt werden.

e) Und welche Anpassung muss bei einer bidirektionalen Beziehung an der ZielentitÀt vorgenommen werden?

  1. Keine, das ist nur bei unidirektionalen Beziehungen erforderlich.
  2. Es muss eine passende Annotation mit der Eigenschaft mappedBy hinzugefĂŒgt werden.
  3. Es muss eine gegenlĂ€ufige Annotation ohne besondere Eigenschaften hinzugefĂŒgt werden.
  4. Es muss mit @ForeignKey ein rĂŒckwĂ€rtsgerichteter FremdschlĂŒssel hinzugefĂŒgt werden.

Lösung:
Aufgabe 3.1: 2, 1, 2, 3
Aufgabe 3.2: 2, 5, 1, 3, 2, 1
Aufgabe 3.3: keine, 1, 3, 1, 2

Daten lesen, schreiben, Àndern

Bildnachweis: Pixabay: rawpixel

Ohne Persistence Unit geht es nicht

Bildnachweis fĂŒr das Endesymbol: Pixabay: janf93

Der Entity Manager stellt sich vor

đŸ—„ïž SĂ€mtliche Datenbankzugriffe werden von einem EntityManager-Objekt durchgefĂŒhrt.
đŸ—„ïž Dieses Objekt stellt aus Anwendungssicht den O/R-Mapper dar, der Daten lesen und schreiben kann.
đŸ—„ïž Mit der Annotation @PersistenceContext können wir uns das Objekt geben lassen.
đŸ—„ïž Die Annotation funktioniert in Serlvets und in EJBs, sollte aber nur in EJBs verwendet werden.
@Stateless public class KinoBean { @PersistenceContext EntityManager em; 
 }

<T> T find(Class<T> entityClass, Object primaryKey)
Auslesen eines Objekts anhand seines PrimĂ€rschlĂŒssels

void persist(Object entity)
Speichern eines neuen Datensatzes.

<T> T merge(<T> entity)
Aktualisieren eines vorhandenen Datensatzes.

void remove(Object entity)
Löschen eines vorhandenen Datensatzes.


Query createQuery(String qlString)
Erzeugen einer neuen Datenbankanfrage (vgl. nÀchste Folie)

@Stateless public class MitarbeiterBean { @PersistenceContext EntityManager em; /** * Auslesen eines einzelnen Mitarbeiters */ public Mitarbeiter findById(long id) { return this.em.find(Mitarbeiter.class, id); } /** * Speichern eines neuen Mitarbeiters und den gespeicherten * Satz zurĂŒckgeben, damit der Aufrufer die ID erfĂ€hrt. */ public Mitarbeiter saveNew(Mitarbeiter mitarbeiter) { em.persist(mitarbeiter); return em.merge(mitarbeiter); } /** * Änderungen an einem vorhandenen Mitarbeiter speichern. */ public Mitarbeiter update(Mitarbeiter mitarbeiter) { return em.merge(mitarbeiter); } /** * Wie lange arbeiten Sie schon hier, morgen nicht mehr * mitgerechnet? 🙇 */ public void delete(Mitarbeiter mitarbeiter) { em.remove(mitarbeiter); } }

Komplexe Anfragen formulieren

Jede Datenbank hat ihren eigenen SQL-Dialekt, was oftmals ziemlich verwirrend sein kann.
JQL hilft uns deshalb und definiert neutrale Anweisungen, die immer funktionieren.

Selektion mit Parametern in SQL:

SELECT * FROM Film WHERE filmname = ? AND jahr BETWEEN ? AND ? AND genre = ?

Selektion mit Parametern in JQL:

SELECT f FROM Film f WHERE f.name = :name AND f.jahr BETWEEN :von AND :bis AND f.genre = :genre

VollstÀndiges Beispiel

@Statless public class JqlSelectBeispiel { @PersistenceContext EntityManager em; public List<Film> sucheFilm(String name, int vonJahr, int bisJahr, Genre genre) { return em.createQuery( "SELECT f FROM Film f" + " WHERE f.name LIKE :name" + " AND f.jahr BETWEEN :von AND :bis" + " AND g.genre = :genre" ) .setParameter("name", name) .setParameter("von", vonJahr) .setParameter("bis", bisJahr) .setParameter("genre", genre) .getResultList(); } }

Sinnvolle Methoden fĂŒr die EJBs

Gute Enterprise Java Beans zu definieren, ist oftmals gar nicht so leicht. FĂŒr viele Anwendungen reicht es jedoch, wenn du je Entity eine Bean mit folgenden Methoden anbietest. Dadurch können die Clients ganz einfach DantesĂ€tze suchen, anlegen, Ă€ndern und löschen. Die Typen Entity und EntityId musst du natĂŒrlich durch eigene Klassen ersetzen.

  • Entity findById(EntityId id)
  • List<Entity> findByXYZ(
)
  • List<Entity> findAll()
  • Entity saveNew(Entity entity)
  • Entity update(Entity entity)
  • void delete(Entity entity)

Vorhandene DatensÀtze finden

Entity findById(EntityId id)
Auslesen eines einzelnen Datensatzes anhand seiner ID

List<Entity> findByXYZ(
)
Spezielle Suchmethoden nach DatensÀtzen anhand irgendwelcher Kriterien

List<Entity> findAll()
Einache Methode zum Auslesen aller DatensÀtze (nur, wenn es nicht zu viele werden können!)


DatensÀtze speichern, Àndern, löschen

Entity saveNew(Entity entity)
Speichern eines komplett neuen Datensatzes, den es zuvor nocht nicht gab

Entity update(Entity entity)
Änderungen an einem zuvor ausgelesenen Datensatz speichern

void delete(Entity entity)
Löschen eines zuvor ausgelesenen Datensatzes


DarĂŒber hinaus kannst du natĂŒrlich noch beliebige, weitere Methoden definieren.

Das nachfolgende Beispiel zeigt fĂŒr jede Methode eine einfache Implementierung.

@Stateless public class StandortBean { @PersistenceContext EntityManager em; // // Vorhandene DatensÀtze finden // public Standort findById(long id) { return em.find(Standort.class, id); } public List<Standort> findByOrt(String ort) { return em.createQuery("SELECT s FROM Standort s WHERE s.adresse.ort = :ort") .setParameter("ort", ort) .getResultList(); } public List<Standort> findAll() { return em.createQuery("SELECT s FROM Standort s") .getResultList(); } // // DatensÀtze speichern, Àndern, löschen // public Standort saveNew(Standort standort) { em.persist(standort); return em.merge(standort); } public Standort update(Standort standort) { return em.merge(standort); } public void delete(Standort standort) { em.remove(standort); } }

Ein wenig Zauberei ✹ mit Vererbung und Generics können dir helfen, dass du nicht immer und immer wieder dieselben Methoden ausprogrammieren musst. Denn das ganze Lesen, Schreiben und Löschen einzelner Entities unterscheidet sich doch im Grunde genommen nur durch den Namen der Entity. So könnte die Basisklasse daher aussehen:

/** * Abstrakte Basisklasse fĂŒr EJBs, die einfach nur Standardmethoden zum Lesen * und Schreiben eines Entity-Typs bietet. * * @param <Entity> Basisklasse der EntitĂ€t * @param <EntityId> Datentyp oder Klasse fĂŒr die SchlĂŒsselwerte */ public abstract class EntityBean<Entity, EntityId> { @PersistenceContext EntityManager em; private final Class<Entity> entityClass; public EntityBean(Class<Entity> entityClass) { this.entityClass = entityClass; } // // Vorhandene DatensĂ€tze finden // public Entity findById(EntityId id) { return em.find(entityClass, id); } public List<Entity> findAll() { String select = "SELECT s FROM $C s".replace("$C", this.entityClass.getName()); return em.createQuery(select).getResultList(); } // // DatensĂ€tze speichern, Ă€ndern, löschen // public Entity saveNew(Entity entity) { em.persist(entity); return em.merge(entity); } public Entity update(Entity entity) { return em.merge(entity); } public void delete(Entity entity) { entity = em.merge(entity); em.remove(entity); } }

Okay, durch die Generics ist die Klasse nicht ganz so einfach. Eine neue EJB fĂŒr eine bestimmte Entity zu definieren, wird damit aber echt zum Kinderspiel. 🎎 Vorfuehrung ist hier der Name der EntitĂ€t und Long der Datentyp ihres PrimĂ€rschlĂŒssels.

@Stateless public class VorfuehrungBean extends EntityBean<Vorfuehrung, Long> { public VorfuehrungBean() { super(Vorfuehrung.class); } public List<Vorfuehrung> findByWochentag(Wochentag wochentag) { return em.createQuery("SELECT v FROM Vorfuehrung v WHERE v.wochentag = :wochentag") .setParameter("wochentag", wochentag) .getResultList(); } // Alle anderen Methoden sind automatisch vorhanden! }

Aufgabe 4: Geht eine EJB ins Kino

Nimm die beiden EntitÀten Film und Mitwirkender von Aufgabe 2 und schreibe je eine Enterprise Java Bean, mit folgenden Methoden.

Klasse FilmBean

  • Film findById(long id)
  • List<Film> findByJahr(int jahr)
  • List<Film> findAll()
  • Film saveNew(Film film)
  • Film update(Film film)
  • void delete(Film film)

Klasse MitwirkenderBean

  • Mitwirkender findById(long id)
  • List<Mitwirkender> findAll()
  • Mitwirkender saveNew(Mitwirkender mitwirkender)
  • Mitwirkender update(Mitwirkender mitwirkender)
  • void delete(Mitwirkender mitwirkender)

Programmiere die beiden Klassen erst komplett von Hand aus, bevor du eine zweite Version auf Grundlage der eben gezeigten, abstrakten EntityBean schreibst.

Hier die Musterlösung fĂŒr die Version mit Copy&Paste.

@Stateless public class FilmBean { @PersistenceContext EntityManager em; // // Vorhandene DatensÀtze finden // public Film findById(long id) { return em.find(Film.class, id); } public List<Film> findByJahr(int jahr) { return em.createQuery("SELECT f FROM Film f WHERE f.jahr = :jahr") .setParameter("jahr", jahr) .getResultList(); } public List<Film> findAll() { return em.createQuery("SELECT f FROM Film f") .getResultList(); } // // DatensÀtze speichern, Àndern, löschen // public Film saveNew(Film film) { em.persist(film); return em.merge(film); } public Film update(Film film) { return em.merge(film); } public void delete(Film film) { em.remove(film); } } @Stateless public class MitwirkenderBean { @PersistenceContext EntityManager em; // // Vorhandene DatensÀtze finden // public Mitwirkender findById(long id) { return em.find(Mitwirkender.class, id); } public List<Mitwirkender> findAll() { return em.createQuery("SELECT m FROM Mitwirkender m") .getResultList(); } // // DatensÀtze speichern, Àndern, löschen // public Mitwirkender saveNew(Mitwirkender mitwirkender) { em.persist(mitwirkender); return em.merge(mitwirkender); } public Mitwirkender update(Mitwirkender mitwirkender) { return em.merge(mitwirkender); } public void delete(Mitwirkender mitwirkender) { em.remove(mitwirkender); } }

Und hier die vereinfachte Version auf Basis unserer selbst-entworfenen EntityBean.

@Stateless public class FilmBean extends EntityBean<Film, Long> { @PersistenceContext EntityManager em; public FilmBean { super(Film.class); } public List<Film> findByJahr(int jahr) { return em.createQuery("SELECT f FROM Film f WHERE f.jahr = :jahr") .setParameter("jahr", jahr) .getResultList(); } } @Stateless public class MitwirkenderBean extends EntityBean<Mitwirkender, Long> { public MitwirkenderBean { super(Mitwirkender.class); } }

Aufgabe 5: Ein kleines Entity-Manager-Quiz

a) Was ist eine Persistence Unit?

  1. Ein alter Name fĂŒr Persistence Entities
  2. Eine Maßeinheit fĂŒr die Dauer der Speicherung
  3. Eine Konfiguration der zu nutzenden Datenbank

b) Wie kannst du eine Referenz auf den Entity Manager bekommen?

  1. @EntityManager PersistenceContext pc;
  2. @PersistenceContext EntityManager em;
  3. @DataSource("jdbc/__default") @EntityManager em;
  4. @PersistenceUnit ObjectMapper om;

c) Welche der folgenden Methoden bietet der Entity Manager fĂŒr die ĂŒblichen CRUD-Operationen?

  1. find(), persist(), update(), delete()
  2. find(), persist(), merge(), remove()
  3. select(), exist(), update(), delete()
  4. find(), persist(), search(), complete()

d) Welche Vorteile hat die Java Persistence Query Language gegenĂŒber SQL?

  1. Keine, da JQL nur eine spezieller SQL-Dialekt ist.
  2. Die Selektionen laufen schneller als mit nativem SQL.
  3. Es bietet eine ĂŒber alle Datenbankhersteller einheitliche Syntax.

e) Und welche Nachteile hat die Java Persistence Query Language gegenĂŒber SQL?

  1. Es ist nicht ganz so schnell wie natives SQL.
  2. Es gibt kein UPDATE und DELETE.
  3. Es funktioniert nicht mit jeder Datenbank.

f) Welches ist keine gĂŒltige JQL-Anfrage?

  1. SELECT * FROM Film f WHERE f.name LIKE ? AND f.jahr BETWEEN ? AND ?
  2. SELECT f FROM Film f WHERE f.genre = :genre AND f.vorfuehrung.wochentag = :wochentag

g) Welche der folgenden Methoden sollte die EJB zu einer Entity mindestens besitzen?

  1. Entity findById(EntityId id)
  2. List<Entity> findByXYZ(
)
  3. List<Entity> findAll()
  4. Entity saveNew(Entity entity)
  5. Entity update(Entity entity)
  6. void delete(Entity entity)

Lösung: 3, 2, 2, 3, 1, 1, alle bis auf 2 und 3

Hinweise zum Schluss

Bildnachweis: Pixabay: rawpixel

Do & Don't

Erstellung des Datenmodells

Konfiguration des O/R-Mappers

Nutzung der Persistence Entities

Rechtshinweise

Creative Commons Namensnennung 4.0 International

§