4. Java Persistence API (JPA) Literatur Klassische Wege zur

Werbung
4. Java Persistence API (JPA)
•
•
•
•
•
•
•
•
•
Literatur
Problem: OO in relationale DB
Idee des Persistence Mapper
Einfache Entity-Klasse
Lebenslauf eines Entity-Objekts
Umsetzung von 1:N- und M:N-Relationen
Geordnete Daten
Anfragen
Vererbung
Validierung
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
• JPA 2.0: Sun Microsystem: JSR 317: JavaTM Persistence 2.0
http://jcp.org/en/jsr/summary?id=317 (persistence2_0_final-spec.pdf ist der Standard; trotzdem sehr gut
lesbar!)
• JPA 1.0: Sun Microsystem, JSR 220: Enterprise JavaBeans,
Version 3.0, Java Persistence API (ejb-3_0-fr-specpersistence.pdf)
http://jcp.org/aboutJava/communityprocess/final/jsr220/index.html
Auch für Teile der weiteren Vorlesung
• C. Bauer, G. King, Java Persistence with Hibernate, Manning,
Greenwich (USA) 2007
• M. Keith, M. Schincariol, Pro EJB 3 - Java Persistence API,
Apress, Berkeley (USA), 2006
114
Klassische Wege zur Verbindung SW und DB
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
115
Nutzung relationaler DB in OO-Programmen
DB werden meist nicht alleine entwickelt, sie müssen mit
umgebender SW integriert werden. Es gibt verschiedene
Ansätze, die gerade bei der Anbindung von OO-SW relevant
sind:
- SW wird (z.B: mit PL/SQL) in der Datenbank entwickelt
(hierzu gibt es auch objektorientierte Ansätze), externe SW
kann auf Prozeduren und Funktionen zugreifen.
- SQL-Aufrufe werden direkt in die SW eingebettet
(Embedded SQL) bzw. Aufrufe werden durch ein einfaches
Framework (z.B. JDBC, SQLJ) gekapselt. (Frage: wie
bekomme ich Objekte in die DB?)
- DB-Entwicklung und SW-Entwicklung wird eng miteinander
verzahnt, hierzu stehen ausgereifte DevelopmentFrameworks zur Verfügung (EJB, .NET)
Oberfläche + Geschäftslogik
• Bisheriger Ansatz:
relationale DB vorhanden,
wird an OO-Programm
angeschlossen.
DB-Zugriffsschicht
• Was ist, wenn DB
zusammen mit OOProgramm entwickelt wird?
• erster Ansatz: warum nicht
genau so vorgehen
z.B. JDBC
relationale Datenbank
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
116
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
117
Aufgabe und Ansatz
Lagerverwaltung (ein kleines OO-Modell)
Lagerraum
-nummer : String
beinhaltet
-artikel
1
*
+verkaufswertBerechnen() : float
+Produkt() : Produkt
+verfuegbareAnzahl() : int
+verkauft(anzahl : int) : void
+einlagern(anzahl : int) : void
+verkaufspreis() : float
- artikel bezeichnet eine Collection von
Produkten in einem Lagerraum
- mit verkaufspreis() wird die
Mehrwertsteuer eingerechnet, die bei
Büchern anders sein soll.
Lebensmittel
-verfallsdatum : Date
+verlustAm(stichtag : Date) : float
Komponentenbasierte SoftwareEntwicklung
Produkt
-name : String
-lagermenge : int
-preis : float
Buch
+verkaufspreis() : float
Prof. Dr.
Stephan Kleuker
118
Umgang mit Kapselung
name
• Anmerkung: Dieser Übersetzungsansatz war ein beliebtes
Tummelfeld von Programmierern ☺, es wird nur ein
möglicher Ansatz vorgestellt.
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
119
Anmerkungen
• Objekte der Klasse Produkt können leicht
in folgende Tabelle übersetzt werden.
Produkt
OID
• Objekte der im Lagerhaltungsmodell vorgestellten Klassen
sollen persistent in einer relationalen DB abgespeichert
werden
• Bei der Anbindung an die Datenbank sollen möglichst viele
OO-Errungenschaften (Kapselung, Vererbung,
Polymorphismus) übernommen werden
• Grundidee: Das Klassendiagramm kann ohne Methoden als
ER-Diagramm gelesen werden, es beinhaltet eine 1:N und
zwei 1:C Beziehungen.
lagermenge
preis
Produkt
-name : String
-lagermenge : int
-preis : float
+Produkt() : Produkt
+verfuegbareAnzahl() : int
+verkauft(anzahl : int) : void
+einlagern(anzahl : int) : void
+verkaufspreis() : float
• Typischerweise werden Klassenvariablen nicht mit den
jeweiligen Objekten abgespeichert, sie müssen explizit
behandelt werden
• Es stellt sich generell die Frage, welche zusätzlichen
Informationen in welchen Tabellen verwaltet werden sollen
• zusätzlicher Primary Key vergeben, falls name nicht eindeutig
• Kapselungsidee wird aufgegeben, da man über die Datenbank
immer direkt auf die Attribute zugreifen kann, nur durch
Mehraufwand einschränkbar:
– Lösung auf der Zugriffsebene im OO-Programm (umgehbar)
– Spezielle Nutzer und Rechte, Nutzung von Views (Aufwand)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
120
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
121
Umgang mit Vererbung
Umgang mit Polymorphismus
• Vererbung kann durch eine
zusätzliche Tabelle mit einer
Referenz implementiert
werden, dabei ist OID Schlüssel
und Fremdschlüssel aus
Produkt
Lebensmittel
OID
• Wie funktioniert Polymorphismus?
Produkt
Lebensmittel
-verfallsdatum : Date
+verlustAm(stichtag : Date) : float
verfallsdatum
• Für jede Vererbungsebene wird eine neue Tabelle benötigt,
was den Zugriff aufwändig macht
• Durch die vorgeschlagene Struktur wird die Sichtweise „ein
Lebensmittel ist ein Produkt“ unterstützt
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
122
Umgang mit Objektidentität
public float verkaufswertBerechnen(){
verkaufswertBerechnen(){
float ergebnis=0.0f;
for(Produkt tmp:artikel)
tmp:artikel)
ergebnis=
ergebnis= ergebnis+
ergebnis+
tmp.verfuegbareAnzahl()*tmp.verkaufspreis();
tmp.verfuegbareAnzahl()*tmp.verkaufspreis();
return ergebnis;
ergebnis;
}
• In der bisherigen relationalen Übersetzung muss
sichergestellt werden, dass das richtige Objekt geladen wird
• Die genaue Klassendefinition bekommt man im
vorgestellten Ansatz nur, wenn man alle zu Unterklassen
gehörigen Tabellen untersucht, ob die OID vorkommt
• Alternativ kann man sich die genaue Klassenzugehörigkeit in
einem weiteren Attribut der Tabelle Produkt merken
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
123
Umgang mit Beziehungen zwischen Objekten
• Objektidentität in der Datenbank wird durch den Primary
Key garantiert
• Wird das Objekt aus der Datenbank gelesen, ist der Nutzer
verantwortlich, dass kein zweites Exemplar des gleichen
Objekts geladen wird
– Alternative: Beim ersten Herauslesen wird ein Flag (ein
zusätzliches Attribut) gesetzt, dass sich das Exemplar
außerhalb der DB befindet (Ansatz fordert viel
Programmierdisziplin, insbesondere bei der
Programmterminierung)
• Exemplarvariablen, die Collections oder andere nicht
triviale Objekttypen enthalten, müssen explizit durch eine
oder mehrere Relationen oder Erweiterungen von
Tabellen modelliert werden (der Ansatz ist vom Übergang
vom ER-Diagramm zur Tabellenstruktur bekannt)
beinhaltet
Lagerraum
-artikel
1
*
Produkt
Produkt
OID
name
lagermenge
preis
lagerraum_OID
• Frage: Warum passt die skizzierte Lösung eigentlich nicht
zum UML-Diagramm?
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
124
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
125
Bearbeiten von Objekten
Zwischenfazit
• Wir haben gesehen, dass einfache Objekte häufig über
mehrere Tabellen verteilt werden, d.h. zum Einlesen und
Bearbeiten sind relativ aufwändige SQL-JOINS notwendig
• Weiterhin muss das OO-Programm die eindeutige Referenz
eines Objektes in der relationalen DB kennen
• Beispiel: Einlesen eines Lebensmittels, das an eine Variable
mit Namen „banane“ gebunden war
SELECT name,
name, lagermenge,
lagermenge, preis, verfallsdatum
FROM Identifikation, Lebensmittel, Produkt
WHERE Identifikation.Name = 'banane
'banane'
banane'
AND Identifikation.OID = Produkt.OID
AND Produkt.OID = Lebensmittel.OID
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
• Selbst bei Beispielen, in denen der Einsatz relationaler
Datenbanken sinnvoller Standard ist (Lagerverwaltung),
wird die Verknüpfung eines OO-Programms mit relationaler
Datenhaltung aufwändig
• Die Nutzung relationaler Datenbanken in OO-Programmen
sollte immer über eine DB-Kapselung, die den Zugriff auf die
DB regelt, erfolgen
• Für den Kapselungsansatz stehen verschiedene Produkte
zur Verfügung (z. B. Hibernate, EclipseLink(TopLink)) , JPA
spezifiziert gemeinsames Interface
• Verwandte Ansätze: direkte Nutzung einer OO-Datenbank,
Programmierung mit JDO
126
Konzept von Persistence-Mappern
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
127
Beispiel (1/7)
• Persistence-Mapper (PM) muss wissen, welche Objekte
persistiert werden sollen -> markiere Klassen, deren
Objekte verwaltet werden sollen
• Persistence-Mapper muss Beziehungen zwischen Objekten
kennen
• Ablauf: Nutzer benutzt Objekt unter Verwaltung; dann
übernimmt PM die Überwachung, führt Änderungen aus,
regelt den Zugriff mehrerer Nutzer
• Ablauf: Nutzer erzeugt Objekt und muss PM einmal
mitteilen, dieses zu verwalten
• PM kann selbst regeln, wie Aufgaben realisiert werden
(Tabellenstruktur, Transaktionssteuerung)
package jpa20beispiel1;
import javax.persistence.Entity;
javax.persistence.Entity;
import javax.persistence.Id;
javax.persistence.Id;
@Entity
public class Mitarbeiter {
@Id private int minr;
minr;
private String name;
name;
public Mitarbeiter(){} //parameterloser Konstruktor benö
benötigt
public Mitarbeiter(int minr,
minr, String name)
name) { //erlaubt
this.minr = minr;
minr;
this.name = name;
name;
}
public
public
public
public
int getMinr()
getMinr() {return
{return minr;}
minr;}
void setMinr(int minr)
minr) {this.minr
{this.minr = minr;}
minr;}
String getName()
getName() {return
{return name;}
name;}
void setName(String name)
name) {this.name
{this.name = name;}
name;}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
128
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
129
Beispiel (2/7)
Beispiel (3/7)
• persistence.xml liegt im Ordner META-INF (projektartabhängig)
• Detaileinstellungen von JPA-Realisierung abhängig (z. B.
EclispeLink (basiert auf TopLink), Hibernate, Apache OpenJPA)
<?xml
<?xml version="1.0" encoding="UTFencoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchemaxmlns: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">
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistencepersistence-unit name="JPA20Beispiel1PU"
transactiontransaction-type="RESOURCE_LOCAL">
type="RESOURCE_LOCAL">
<provider>
provider>
org.eclipse.persistence.jpa.PersistenceProvider
</provider
</provider>
provider>
<class>jpa20beispiel1.Mitarbeiter</class>
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
130
Beispiel (4/7)
<properties>
properties>
<property name="javax.persistence.jdbc.url"
name="javax.persistence.jdbc.url"
value="jdbc:derby://localhost:1527/Spielerei2"/>
<property name="javax.persistence.jdbc.password"
name="javax.persistence.jdbc.password"
value="kleuker"/>
value="kleuker"/>
<property name="javax.persistence.jdbc.driver"
name="javax.persistence.jdbc.driver"
value="org.apache.derby.jdbc.ClientDriver"/>
value="org.apache.derby.jdbc.ClientDriver"/>
<property name="javax.persistence.jdbc.user"
name="javax.persistence.jdbc.user"
value="kleuker"/>
value="kleuker"/>
<property name="eclipselink.ddlname="eclipselink.ddl-generation"
generation"
value="dropvalue="drop-andand-createcreate-tables"/>
tables"/>
</properties
</properties>
properties>
</persistence
</persistencepersistence-unit>
unit>
</persistence
</persistence>
persistence>
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
131
Beispiel (5/7)
package jpa20beispiel1;
import java.util.List;
java.util.List;
import javax.persistence.EntityManager;
javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
javax.persistence.Persistence;
public class Main {
private EntityManagerFactory emf = Persistence.
Persistence.
createEntityManagerFactory("JPA20Beispiel1PU");
private EntityManager em = emf.createEntityManager();
emf.createEntityManager();
public void beispieldaten()
beispieldaten() {
String namen[]
namen[] = {"Egon", "Erwin", "Ute", "Aische
"Aische"};
Aische"};
em.getTransaction().begin();
em.getTransaction().begin();
for (int i=0; i<namen.length;
i<namen.length; i++)
em.persist(new Mitarbeiter(i,namen[i]));
Mitarbeiter(i,namen[i]));
em.getTransaction().commit();
em.getTransaction().commit();
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
132
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
133
Beispiel (6/7)
Beispiel (7/7)
public void datenZeigen()
datenZeigen() {
for (Mitarbeiter m : em.createQuery(
em.createQuery(
"SELECT m FROM Mitarbeiter m",Mitarbeiter.class)
m",Mitarbeiter.class)
.getResultList())
getResultList()) {
System.out.println(m.getMinr()
System.out.println(m.getMinr() + ": " + m.getName());
m.getName());
}
}
• Falls noch keine Tabelle Mitarbeiter existiert, wird diese
angelegt
public void schliessen()
schliessen() {
if (em != null && em.isOpen())
em.isOpen()) {em.close
{em.close();}
em.close();}
if (emf != null && emf.isOpen())
emf.isOpen()) {emf.close
{emf.close();}
emf.close();}
}
public static void main(String[]
main(String[] args)
args) {
Main m = new Main();
m.beispieldaten();
m.beispieldaten();
m.datenZeigen();
m.datenZeigen();
m.schliessen();
m.schliessen();
}
0:
1:
2:
3:
Egon
Erwin
Ute
Aische
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
134
Suchen und Bearbeiten von einzelnen Objekten
Prof. Dr.
Stephan Kleuker
Prof. Dr.
Stephan Kleuker
135
Zentrale Klassen
public void namenAendern(){
namenAendern(){
int eingabe=eingabe=-1;
while(eingabe!=0){
System.out.print("Welche Nummer (Ende mit 0): ");
eingabe=new Scanner(System.in).nextInt();
Scanner(System.in).nextInt();
Mitarbeiter m = em.find(Mitarbeiter.class,
em.find(Mitarbeiter.class, eingabe);
eingabe);
if(m == null)
System.out.println("Witzbold");
System.out.println("Witzbold");
else{
else{
System.out.print("Neuer Name (alt:"+m.getName
(alt:"+m.getName()+"):
alt:"+m.getName()+"): ");
String name=new Scanner(System.in).next();
Scanner(System.in).next();
EntityTransaction tr = em.getTransaction();
em.getTransaction();
tr.begin();
tr.begin();
m.setName(name);
m.setName(name);
tr.commit();
tr.commit();
}
}
}
Komponentenbasierte SoftwareEntwicklung
Komponentenbasierte SoftwareEntwicklung
136
• Aufbau der Verbindung
EntityManagerFactory emf = Persistence.
Persistence.
createEntityManagerFactory("JPA20Beispiel1PU");
– Einrichtung sehr aufwändig, selten neu erstellen
• Einrichtung der Verbindung für den Nutzer
EntityManager em = emf.createEntityManager();
emf.createEntityManager();
– kostet Zeit, gibt trotzdem meist sinnvoll Objekt häufiger
zu erzeugen und zu schließen/löschen
• Nutzung einer Transaktion
EntityTransaction tr = em.getTransaction();
em.getTransaction();
– kurzfristig nutzen: Daten vorbereiten, dann DB-Zugriff,
dann schließen
• immer alles schließen (typisch in finally-Block)
• Generelles Verhalten hängt von DB ab, in Großprojekten
immer mit erfahrenem DB-Administrator arbeiten
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
137
Entity ist POJO
Primary Key
Entity-Klassen-Objekte sind klassische Plain Old Java Objects
• Verpflichtung:
– public (oder protected) Konstruktor ohne Parameter
– Exemplarvariablen private oder protected, Zugriff über
get... und set...
– Klasse, Methoden, Exemplarvariablen nicht final
– Serialisierbar [zumindest sehr sinnvoll]
• keine weiteren Einschränkungen
– beliebige weitere Methoden
– Vererbung (auch Entity von Nicht-Entity [aufwändig])
– Nutzung abstrakte Klassen
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
Typisch: Primary Key wird erzeugt
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
GenerationType.AUTO)
private int minr;
minr;
folgende Datentypen erlaubt
• primitive Java-Typen (int, long, …)
• Wrapper von primitiven Java-Typen (Integer, Long, …)
• java.lang.String
• java.util.Date
• java.sql.Date
138
Persistierbare Typen/Klassen
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
139
Persistent Fields / Properties
• Primitive Typen (byte, char, int, long, float, double, boolean)
• java.lang.String
• Andere serialisierbare Typen:
– Wrapper der primitiven Typen
– java.math.BigInteger
– java.math.BigDecimal
– java.util.Date, java.util.Calendar
– java.sql.Date, java.sql.Time, java.sql.TimeStamp
– Nutzerdefinierte serialisierbare Typen
– byte[], Byte[], char[], Character[]
• Enumeration
• Andere Entities
• Collections von Entities (Collection, Set, List, Map)
• Persistent Fields
– @Id private int minr;
minr;
– Exemplarvariablen direkt annotiert
– Zugriff des Persistence-Frameworks direkt auf Variablen
– Vorteil: Variable und Annotation stehen direkt
zusammen
• Persistent Properties
– @Id public int getMinr{
getMinr{ return this.minr;
this.minr; }
– get-Methode wird annotiert
– Zugriff auf Exemplarvariablen muss immer über Standard
get erfolgen (auch in der Klasse selbst)
– Vorteil: Flexibilität, da Methode weitere Funktionalität
haben kann
Komponentenbasierte SoftwareEntwicklung
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
140
Prof. Dr.
Stephan Kleuker
141
Annotationen zur Flexibilisierung / Ergänzung
PersistenceContext
@Entity
@Table(name="Chef")
Table(name="Chef")
public class Mitarbeiter implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
GenerationType.AUTO)
private int minr;
minr;
@Column(name="Leiter",
Column(name="Leiter", nullable=false,
nullable=false,
updatable=true,
updatable=true, unique=true)
unique=true)
private String name;
name;
...
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
•
•
•
•
•
em.getTransaction().commit();
em.getTransaction().commit();
• Beim Schreiben können wg. der Transaktionssteuerung der
DB Exceptions auftreten (abhängig von Steuerungsart)
• Im Zweifel bei Entwicklung immer echte Tabellen anschauen
• Üblich: nur kurz lebende EntityManager (erzeugen, Aktion,
schließen)
142
Beispiel für Cache (1/2)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
143
Beispiel für Cache (2/2)
public static void main(String[]
main(String[] args)
args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("JPACachePU");
Persistence.createEntityManagerFactory("JPACachePU");
EntityManager em=emf.createEntityManager();
em=emf.createEntityManager();
em.getTransaction().begin();
em.getTransaction().begin();
em.persist(new Mitarbeiter("ET"));
Mitarbeiter("ET"));
em.persist(new Mitarbeiter("JFK"));
Mitarbeiter("JFK"));
for(int i=1;i<3;i++)
System.out.println(em.find(Mitarbeiter.class,i));
System.out.println(em.find(Mitarbeiter.class,i));
em.getTransaction().commit();
em.getTransaction().commit();
em.close();
em.close();
} ET(1)
}
JFK(2)
[EL Warning]:
Warning]: 20102010-0303-28 13:12:57.86813:12:57.868-UnitOfWork(20290587)UnitOfWork(20290587)Exception [EclipseLink[EclipseLink-4002]:
org.eclipse.persistence.exceptions.DatabaseException
Internal Exception:
Exception: java.sql.SQLDataException:
java.sql.SQLDataException: Bei dem
Versuch, VARCHAR 'JFK' auf die Lä
Länge 2 zu kü
kürzen, ist
ein Abschneidefehler aufgetreten.
@Entity
public class Mitarbeiter implements Serializable {
@Id @GeneratedValue
private int minr;
minr;
@Column(length=2) //maximal zwei Zeichen
private String name;
name;
public Mitarbeiter() {
} //parameterloser Konstruktor benö
benötigt
public Mitarbeiter(String name)
name) {
this.name = name;
name;
}
// getget- und setset-Methoden weggelassen
@Override public String toString(){
toString(){
return name+"("+minr+")";
name+"("+minr+")";
}
Komponentenbasierte
Software}
Entwicklung
Wird für Objekte vorher festgehaltener Klassen definiert
Entspricht einem Cache, der MANAGED-Objekte verwaltet
EntityManager-Objekt für konkreten PersistenceContext
EntityManager-Operationen arbeiten auf dem Cache
Man muss EntityManager mitteilen, dass Daten in die DB
geschrieben werden müssen
Prof. Dr.
Stephan Kleuker
144
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
145
Lebenslauf eines Entity-Objekts
Unterschied: Lokal und DB immer beachten
NEW -> merge() führt evtl. zur Mehrfachobjekterzeugung
refresh() nur, wenn vorher persistiert
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
146
Sauberes Persistieren
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
147
Klasse oder Tabelle?
• Auslagerung der Persistierung in eine Methode mit sauberer
Exception-Behandlung
public void persist(Object object)
object) {
em.getTransaction().begin();
em.getTransaction().begin();
try {
em.persist(object);
em.persist(object);
em.getTransaction().commit();
em.getTransaction().commit();
} catch (Exception
(Exception e) {
e.printStackTrace();
e.printStackTrace();
em.getTransaction().rollback();
em.getTransaction().rollback();
} finally {
em.close();
em.close();
}
}
Komponentenbasierte SoftwareEntwicklung
public static void main(String[]
main(String[] args)
args) {
EntityManagerFactory emf = Persistence.
Persistence.
createEntityManagerFactory("JPA20Beispiel1PU");
EntityManager em = emf.createEntityManager();
emf.createEntityManager();
Mitarbeiter m1 = new Mitarbeiter("Ford");
Mitarbeiter("Ford");
Mitarbeiter m2 = new Mitarbeiter("Arthur");
Mitarbeiter("Arthur");
em.getTransaction().begin();
em.getTransaction().begin();
2: Arthur
em.persist(m1);
1: Manta
em.persist(m2);
Ford(1)
em.getTransaction().commit();
em.getTransaction().commit();
m1.setName("Manta");
for (Mitarbeiter m : em.createQuery(
em.createQuery(
"SELECT m FROM Mitarbeiter m", Mitarbeiter.class)
Mitarbeiter.class)
.getResultList())
getResultList())
System.out.println(m.getMinr()
System.out.println(m.getMinr()
+ ": " + m.getName());
m.getName());
em.close();
em.close();
em.refresh(m1);
System.out.println(m1);
}
Prof. Dr.
Stephan Kleuker
148
Bei der Entity-Nutzung offen, ob erst Klassen designt und dann
Tabellen entworfen werden
• Einfach: Tabellen existieren; dann typischerweise zur
Tabelle eine Entity-Klassse erstellbar (generierbar)
• Wenn nichts gegeben:
– Entwurf der Entity-Klassen (Daten der Applikation mit
ihren Abhängigkeiten)
– Ableitung oder Generierung der Tabellen
• Generierungsansätze:
– Drop and Create: beteiligte Tabellen löschen und neu
anlegen (Entwicklung und Test)
– Create: wenn nicht existent, dann anlegen (Realität)
– None: wenn nicht existent, dann Fehler (Realität)
• Hinweis: bei Änderungen neu übersetzen
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
149
Generelle JEE-Regel
Einschub: XML-Konfiguration
Convention over Configuration
• bedeutet: wenn nichts angegeben wird, wird ein DefaultWert genutzt
• Default-Werte sind zwar sinnvoll, sollte man aber kennen
• Statt Annotationen zu nutzen, können diese Informationen
auch in XML beschrieben werden
• Typisch: eine XML-Datei pro Klasse + zusammenführende
XML-Datei
• Vorteil: Verhaltensänderungen ohne Codeänderung
• Nachteil: viele kleine penibel zu pflegende Dateien
• Erinnerung: Java-Inkonsistenz
• Auch möglich: XML und Annotationen; dabei „schlägt“ XML
die Annotationen
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
150
Kardinalitäten in JPA
A
Prof. Dr.
Stephan Kleuker
151
Umsetzung unidirektional 1:N (1/4)
x
y
B
• 1:1 zu einem A- Objekt gehört (maximal) ein anderes BObjekt, die jeweils zu (maximal) einem A-Objekt gehören
• 1:N zu einem A-Objekt gehören beliebig viele B-Objekte, die
jeweils zu (maximal) einem A-Objekt gehören (N:1 analog)
• M:N zu einem A-Objekt gehören beliebig viele B-Objekte, die
jeweils zu beliebig vielen A-Objekten gehören
• Anders als bei Tabellen haben OO-Assoziationen
Leserichtungen
• Unidirektional: nur von einer Seite auf die andere schließbar
• Bidirektional: Abhängigkeit in beide Richtungen manövrierbar
(es gibt Besitzer der Beziehung; für Updates wichtig)
Komponentenbasierte SoftwareEntwicklung
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
152
@Entity
public class Projektauftrag implements Serializable {
@Id @GeneratedValue private int paid;
paid;
private String titel;
titel;
public String getTitel()
getTitel() {
return titel;
titel;
}
public void setTitel(String titel)
titel) {
this.titel = titel;
titel;
}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
153
Umsetzung unidirektional 1:N (2/4)
Umsetzung unidirektional 1:N (3/4)
public void datenAnlegen(){
datenAnlegen(){
String p[] ={"Hotel", "Noten", "Belegung"};
String a[] ={"Analyse", "Modell", "Design"};
em.getTransaction().begin();
em.getTransaction().begin();
for (int i=0; i<p.length;i++){
i<p.length;i++){
Projekt pr=
pr= new Projekt();
pr.setName(p[i]);
pr.setName(p[i]);
for (int j=0; j<a.length;j++){
j<a.length;j++){
Projektauftrag pa=
pa= new Projektauftrag();
pa.setTitel(a[j]);
pa.setTitel(a[j]);
Set<Projektauftrag>
Set<Projektauftrag> tmp=
tmp= pr.getAuftraege();
pr.getAuftraege();
tmp.add(pa);
tmp.add(pa);
}
em.persist(pr);
em.persist(pr);
}
em.getTransaction().commit();
em.getTransaction().commit();
}
@Entity
public class Projekt implements Serializable {
@Id @GeneratedValue
private int projektid;
projektid;
private String name;
name;
@OneToMany(cascade=CascadeType.PERSIST)
OneToMany(cascade=CascadeType.PERSIST)
private Set<Projektauftrag>
Set<Projektauftrag> auftraege;
auftraege;
public Projekt(){
auftraege = new HashSet<Projektauftrag>();
HashSet<Projektauftrag>();
}
// fehlen getget- und setset-Methoden
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
154
Umsetzung unidirektional 1:N (4/4)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
155
Cascade-Varianten
SELECT PROJEKT.PROJEKTID, PROJEKT.NAME, PROJEKTAUFTRAG.PAID,
PROJEKTAUFTRAG.TITEL
FROM PROJEKT, PROJEKT_PROJEKTAUFTRAG, PROJEKTAUFTRAG
WHERE PROJEKT.PROJEKTID =
PROJEKT_PROJEKTAUFTRAG.PROJEKT_PROJEKTID
AND PROJEKTAUFTRAG.PAID =
PROJEKT_PROJEKTAUFTRAG.AUFTRAEGE_PAID;
@OneToMany(cascade={CascadeType.PERSIST,
OneToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE})
CascadeType.MERGE})
• MERGE : merge() implizit für verknüpfte Objekte aufrufen
• PERSIST: persist() implizit für verknüpfte Objekte aufrufen
• REFRESH: refresh() implizit für verknüpfte Objekte aufrufen
• REMOVE: remove() implizit für verknüpfte Objekte aufrufen
• ALL: alle vier genannten Möglichkeiten
• Default-Einstellung: keine der fünf Varianten
• Wichtige Design-Entscheidung, was sinnvoll ist
• REMOVE nur bei @OneToOne und @OneToMany nutzbar
Beispiel, wenn Cascade.PERSIST fehlte
SCHWERWIEGEND: Could not synchronize database state with session
org.hibernate.TransientObjectException:
: object references an unsaved
org.hibernate.TransientObjectException
transient instance - save the transient instance before flushing:
flushing:
Projektauftrag
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
156
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
157
Umsetzung bidirektional 1:N (1/5)
Umsetzung bidirektional 1:N (2/5)
@Entity
public class Projektauftrag implements Serializable {
@Id @GeneratedValue
private int paid;
paid;
- Bidirektionale Relationen haben
Eigentümer (owner side) und
Abhängigen (inverse side)
- Eigentümer bei 1:N ist N (hier
Projektauftrag)
- Abhängiger bekommt
mappedBy-Attribut
- Programmierer für Einträge auf
beiden Seiten verantwortlich
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
private String titel;
titel;
@ManyToOne(cascade={CascadeType.PERSIST,
ManyToOne(cascade={CascadeType.PERSIST,
CascadeType.MERGE})
CascadeType.MERGE})
private Mitarbeiter bearbeiter;
bearbeiter;
//...
158
Umsetzung bidirektional 1:N (3/5)
public void auftragHinzu(Projektauftrag pa){
pa){
getAuftraege().add(pa);
getAuftraege().add(pa);
}
// ...
Prof. Dr.
Stephan Kleuker
Prof. Dr.
Stephan Kleuker
159
Umsetzung bidirektional 1:N (4/5)
@Entity
public class Mitarbeiter implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
GenerationType.AUTO)
private int minr;
minr;
private String name;
name;
@OneToMany(mappedBy="bearbeiter",
OneToMany(mappedBy="bearbeiter",
cascade={CascadeType.PERSIST,
cascade={CascadeType.PERSIST, CascadeType.MERGE})
CascadeType.MERGE})
private Set<Projektauftrag>
Set<Projektauftrag> auftraege =
new HashSet<Projektauftrag>();
HashSet<Projektauftrag>();
Komponentenbasierte SoftwareEntwicklung
Komponentenbasierte SoftwareEntwicklung
160
public void mitarbeiterZuordnen(){
mitarbeiterZuordnen(){
Mitarbeiter m[]= {new
{new Mitarbeiter("Egon"),
Mitarbeiter("Egon"),
new Mitarbeiter("Aische"),
Mitarbeiter("Aische"), new Mitarbeiter("Urs")};
Mitarbeiter("Urs")};
em.getTransaction().begin();
em.getTransaction().begin();
for (int i=0; i<m.length;
i<m.length; i++)
em.persist(m[i]);
em.persist(m[i]);
for (Projektauftrag p : (List<Projektauftrag
(List<Projektauftrag>)
List<Projektauftrag>) em.
createQuery("SELECT p FROM Projektauftrag p").
getResultList())
getResultList())
if(p.getTitel().equals("Analyse")){
if(p.getTitel().equals("Analyse")){
JPA 1.0m[0].auftragHinzu(p);
Variante der
p.setBearbeiter(m[0]);
Anfrage
}
else{
else{
m[1].auftragHinzu(p);
p.setBearbeiter(m[1]);
}
em.getTransaction().commit();
em.getTransaction().commit();
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
161
Umsetzung bidirektional 1:N (5/5)
Umsetzung ist OR-Mapper freigestellt
SELECT MITARBEITER.NAME, MITARBEITER.MINR,
PROJEKTAUFTRAG.TITEL, PROJEKTAUFTRAG.PAID
FROM MITARBEITER LEFT JOIN PROJEKTAUFTRAG
ON (MITARBEITER.MINR=PROJEKTAUFTRAG.BEARBEITER_MINR) ;
Man sieht, dass bei
bidirektionalen
Beziehungen in
EclipseLink keine
neuen Tabellen
angelegt werden
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
162
Wichtige Annotationen in Großprojekten
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
163
Umsetzung bidirektional M:N (1/6)
@Version int version
• Attribut wird für das optimistische Locking genutzt; erst
beim Schreiben geschaut, ob sich Versionsnummer
geändert hat
• performant, wenn Objekte nicht häufig geändert werden
• Einfach als zusätzliches Attribut ergänzen
@Basic(fetch=FetchType.LAZY)
Basic(fetch=FetchType.LAZY)
private Set<Projektauftrag>
Set<Projektauftrag> auftraege
• EAGER: Alle Daten des Attributs werden bei Objektnutzung
sofort in Hauptspeicher geladen (default)
• LAZY: Daten werden erst geladen, wenn benötigt
• Längere Listen oder komplexe Daten möglichst immer LAZY
(versteckte Konsistenzprobleme möglich)
• Wenn eine Info sofort benötigt, ist Kette zur Info EAGER
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
164
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
165
Umsetzung bidirektional M:N (2/6)
Umsetzung bidirektional M:N (3/6)
@Entity
public class Rolle implements Serializable {
@ID @GeneratedValue
@GeneratedValue private int rid;
rid;
private String name;
name;
private int tagessatz;
tagessatz;
@ManyToMany(cascade = {CascadeType.PERSIST
{CascadeType.PERSIST,
CascadeType.PERSIST,
CascadeType.MERGE})
CascadeType.MERGE})
@Basic(fetch = FetchType.LAZY)
FetchType.LAZY)
private List<Mitarbeiter>
List<Mitarbeiter> mitarbeiter;
mitarbeiter;
@Version private int version;
version;
@Entity
public class Projektauftrag implements Serializable {
@Id @GeneratedValue
private int paid;
paid;
private String titel;
titel;
@ManyToOne(cascade={CascadeType.PERSIST,
ManyToOne(cascade={CascadeType.PERSIST,
CascadeType.MERGE})
CascadeType.MERGE})
private Mitarbeiter bearbeiter;
bearbeiter;
@ManyToOne(cascade={CascadeType.PERSIST,
ManyToOne(cascade={CascadeType.PERSIST,
CascadeType.MERGE})
CascadeType.MERGE})
private Rolle rolle;
rolle;
@Version
private int version;
version;
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
public void mitarbeiterHinzu(Mitarbeiter m) {
List<Mitarbeiter>
List<Mitarbeiter> tmp = getMitarbeiter();
getMitarbeiter();
tmp.add(m);
tmp.add(m);
setMitarbeiter(tmp);
setMitarbeiter(tmp); // oder getMitarbeiter().add(m);
getMitarbeiter().add(m);
}
166
Umsetzung bidirektional M:N (4/6)
Prof. Dr.
Stephan Kleuker
Prof. Dr.
Stephan Kleuker
167
Umsetzung bidirektional M:N (5/6)
@Entity
public class Mitarbeiter implements Serializable {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
GenerationType.AUTO)
private int minr;
minr;
private String name;
name;
@OneToMany(mappedBy = "bearbeiter
"bearbeiter",
bearbeiter",
cascade = {CascadeType.PERSIST
{CascadeType.PERSIST,
CascadeType.PERSIST, CascadeType.MERGE})
CascadeType.MERGE})
@Basic(fetch = FetchType.LAZY)
FetchType.LAZY)
private Set<Projektauftrag>
Set<Projektauftrag> auftraege;
auftraege;
@ManyToMany(mappedBy = "mitarbeiter
"mitarbeiter",
mitarbeiter",
cascade = {CascadeType.PERSIST
{CascadeType.PERSIST,
CascadeType.PERSIST, CascadeType.MERGE})
CascadeType.MERGE})
@Basic(fetch = FetchType.LAZY)
FetchType.LAZY)
private Set<Rolle>
Set<Rolle> rollen = new HashSet<Rolle>();
HashSet<Rolle>();
@Version private int version;
version;
public void rolleHinzu(Rolle r) {
Set<Rolle>
Set<Rolle> tmp = getRollen();
getRollen();
tmp.add(r);
tmp.add(r);
setRollen(tmp);
setRollen(tmp);
}
Komponentenbasierte SoftwareEntwicklung
Komponentenbasierte SoftwareEntwicklung
public void mitarbeiterZuordnen()
mitarbeiterZuordnen() {
Mitarbeiter m[] = {new
{new Mitarbeiter("Egon"),
Mitarbeiter("Egon"),
new Mitarbeiter("Aische"),
Mitarbeiter("Aische"), new Mitarbeiter("Urs")};
Mitarbeiter("Urs")};
em.getTransaction().begin();
em.getTransaction().begin();
for (int i = 0; i < m.length;
m.length; i++)
em.persist(m[i]);
em.persist(m[i]);
Rolle r[] = {new
{new Rolle("OOAD",80), new Rolle("SVN",60),
new Rolle("QS",100)};
for (int i = 0; i < r.length;
r.length; i++) {
for(int j = 0; j<m.length;
j<m.length; j++)
if(r[i].getName().length()<m[j].getName().length()){
if(r[i].getName().length()<m[j].getName().length()){
r[i].mitarbeiterHinzu(m[j]);
r[i].mitarbeiterHinzu(m[j]);
m[j].rolleHinzu(r[i]);
m[j].rolleHinzu(r[i]);
}
em.persist(r[i]);
em.persist(r[i]);
}
// Rollenzuordnung wie bei 1:N mitarbeiterZuordnen()
mitarbeiterZuordnen()
em.getTransaction().commit();
em.getTransaction().commit();
}
168
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
169
Umsetzung bidirektional M:N (6/6)
Komponentenbasierte SoftwareEntwicklung
Tabellenstruktur
Prof. Dr.
Stephan Kleuker
170
Ungeordnete Daten (1/4)
@Entity
public class Punkt implements Serializable{
Serializable{
@Id @GeneratedValue
private int id;
id;
private int x;
private int y;
@Version
private int version;
version;
public Punkt(int x, int y) {
this.x = x;
this.y = y;
}
public Punkt(){}
Prof. Dr.
Stephan Kleuker
Prof. Dr.
Stephan Kleuker
171
Ungeordnete Daten (2/4)
Problem: Auch
Listen werden
ungeordnet
gespeichert
@Entity
public class Polygon implements Serializable{
Serializable{
@Id @GeneratedValue
private int id;
id;
@OneToMany(cascade = {CascadeType.PERSIST
{CascadeType.PERSIST,
CascadeType.PERSIST, CascadeType.MERGE})
CascadeType.MERGE})
private List<Punkt>
List<Punkt> punkte= new ArrayList<Punkt>();
ArrayList<Punkt>();
@Version private int version;
version;
//get
public Polygon(){} //
get und set für Exemplarvariablen
public void punkteHinzu(Punkt...
punkteHinzu(Punkt... pkte){
pkte){
for(Punkt p:pkte)
p:pkte)
punkte.add(p);
punkte.add(p);
}
@Override public String toString(){
toString(){
StringBuffer erg=new StringBuffer("<");
StringBuffer("<");
for(Punkt p:punkte)
p:punkte)
erg.append(p.toString());
erg.append(p.toString());
return erg.append(">").toString();
erg.append(">").toString();
}
@Override
public String toString(){
toString(){
return "["+x+","+y
"["+x+","+y+"]";
x+","+y+"]";
}
// getget- und setset-Methoden für Exemplarvariablen
Komponentenbasierte SoftwareEntwicklung
Komponentenbasierte SoftwareEntwicklung
172
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
173
Ungeordnete Daten (3/4)
Ungeordnete Daten (4/4)
public class Main {
private EntityManagerFactory emf =
Persistence.createEntityManagerFactory("JPA20NeueFeaturesPU");
Persistence.createEntityManagerFactory("JPA20NeueFeaturesPU");
private EntityManager em = emf.createEntityManager();
emf.createEntityManager();
objekteErzeugen(){
public void objekteErzeugen
(){
Punkt[] pkt={new Punkt(0,0), new Punkt(5,3),
new Punkt(3,3), new Punkt(3,0)};
em.getTransaction().begin();
em.getTransaction().begin();
for(Punkt p:pkt)
p:pkt)
em.persist(p);
em.persist(p);
em.getTransaction().commit();
em.getTransaction().commit();
Polygon p1 = new Polygon();
p1.punkteHinzu(pkt[0],pkt[1],pkt[2]);
Polygon p2 = new Polygon();
p2.punkteHinzu(pkt[3],pkt[2],pkt[1]);
em.getTransaction().begin();
em.getTransaction().begin();
em.persist(p1);
em.persist(p2);
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
174
Ordnung beibehalten
public void schliessen()
schliessen() {
if (em != null && em.isOpen())
em.isOpen()) em.close();
em.close();
if (emf != null && emf.isOpen())
emf.isOpen()) emf.close();
emf.close();
}
public static void main(String[]
args)
) {
main(String[] args
Main m= new Main();
m.objekteErzeugen();
m.objekteErzeugen();
<[0,0][5,3][3,3]>
m.zeigePolygone();
m.zeigePolygone();
<[3,0][3,3][5,3]>
m.schliessen();
m.schliessen();
---System.out.println("
System.out.println("---("----");
----");
<[0,0][5,3][3,3]>
m= new Main();
m.zeigePolygone();
m.zeigePolygone();
<[5,3][3,3][3,0]>
m.schliessen();
m.schliessen();
Komponentenbasierte
SoftwareProf. Dr.
175
}
Entwicklung
Stephan Kleuker
}
Standard: keine Löschfortsetzung (1/2)
@Entity
public class Polygon implements Serializable{
Serializable{
@Id @GeneratedValue
private int id;
id;
@OneToMany(cascade = {CascadeType.PERSIST
{CascadeType.PERSIST,
CascadeType.PERSIST, CascadeType.MERGE})
CascadeType.MERGE})
@OrderColumn(name="Ord")
OrderColumn(name="Ord")
private List<Punkt>
List<Punkt> punkte= new ArrayList<Punkt>();
ArrayList<Punkt>();
@Version
private int version;
version;
public void objekteErzeugen()
objekteErzeugen() {
Punkt[] pkt = {new
{new Punkt(0, 0), new Punkt(5, 3),
new Punkt(3, 3)};
Polygon p1 = new Polygon();
p1.punkteHinzu(pkt[0], pkt[1], pkt[2]);
em.getTransaction().begin();
em.getTransaction().begin();
em.persist(p1);
em.getTransaction().commit();
em.getTransaction().commit();
}
public void objekteBearbeiten()
objekteBearbeiten() {
Polygon pl = em.createQuery("SELECT p FROM Polygon p",
Polygon.class).getResultList().get(0);
pl.getPunkte().remove(1);
em.getTransaction().begin();
em.getTransaction().begin();
em.persist(pl);
em.persist(pl);
em.getTransaction().commit();
em.getTransaction().commit();
}
<[3,0][3,3][5,3]>
<[0,0][5,3][3,3]>
---<[3,0][3,3][5,3]>
<[0,0][5,3][3,3]>
Komponentenbasierte SoftwareEntwicklung
public void zeigePolygone(){
zeigePolygone(){
List<Polygon>
List<Polygon> pl = em.createQuery(
em.createQuery(
"SELECT p FROM Polygon p",Polygon.class).getResultList();
p",Polygon.class).getResultList();
for(Polygon po:pl)
po:pl)
System.out.println(po);
System.out.println(po);
}
Prof. Dr.
Stephan Kleuker
176
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
177
Standard: keine Löschfortsetzung (2/2)
Löschfortsetzung
public void zeigePolygoneUndPunkte()
zeigePolygoneUndPunkte() {
for (Polygon po : em.createQuery("SELECT p FROM Polygon p",
Polygon.class).getResultList())
Polygon.class).getResultList())
System.out.println(po);
System.out.println(po);
System.out.println("
System.out.println("---("----");
----");
for (Punkt pu : em.createQuery("SELECT p FROM Punkt p",
Punkt.class).getResultList())
Punkt.class).getResultList())
System.out.println(pu);
System.out.println(pu);
}
public static void main(String[]
main(String[] args)
args) {
Main m = new Main();
m.objekteErzeugen();
m.objekteErzeugen();
m.objekteBearbeiten();
m.objekteBearbeiten();
m.zeigePolygoneUndPunkte();
m.zeigePolygoneUndPunkte();
m.schliessen();
m.schliessen();
}
Komponentenbasierte SoftwareEntwicklung
Projekt
178
Komponentenbasierte SoftwareEntwicklung
Projektauftrag
Prof. Dr.
Stephan Kleuker
179
• Anfragesprache soll möglichst Entity-Objekte liefern
• Anfragesprache soll DB-unabhängig sein (SQL-Detailproblem)
• Antwort: Java Persistence QL (JPQL)
– Ermöglicht direkte Zurückgabe von Entitätsobjektlisten
– Ermöglicht auch direkte Ausführung von SQL-Anfragen
• Anmerkung: Vorgänger JDO unterstützte OO-Features in JDOQL (Methodennutzung); dies ist nicht mehr möglich
• Typische Struktur:
– SELECT p FROM Projekt p WHERE <Bed
<Bed>
Bed>
– Übersetzung: Wähle aus der Menge Projekt der
gemanageten Objekte die Elemente p mit Eigenschaft
<Bed>
Projekt_Projektauftrag
Prof. Dr.
Stephan Kleuker
Komponentenbasierte SoftwareEntwicklung
Anfragen
Rolle
Rolle_Mitarbeiter
<[0,0][3,3]>
---[0,0]
[3,3]
Exception in thread "main“
"main“ javax.persistence.RollbackException
Caused by:
by: java.sql.SQLIntegrityConstraintViolationException:
java.sql.SQLIntegrityConstraintViolationException:
DELETE in Tabelle 'PUNKT' hat fü
für Schlü
Schlüssel (3) die
Integritä
Integritätsbedingung 'PLYGONPUNKTPNKTEID' fü
für Fremdschlü
Fremdschlüssel
verletzt.
Beispieldaten (in DB-Notation, rauslegen)
Mitarbeiter
@OneToMany(cascade = {
{CascadeType.ALL
CascadeType.ALL},
CascadeType.ALL},
orphanRemoval=true)
orphanRemoval=true)
@OrderColumn(name="Ord")
OrderColumn(name="Ord")
private List<Punkt>
List<Punkt> punkte =
new ArrayList<Punkt>();
ArrayList<Punkt>();
• Was passiert, wenn mehrere Objekte Punkt referenzieren
(widerspricht der Eigentümerschaft)?
<[0,0][3,3]>
---[0,0]
[5,3]
[3,3]
Prof. Dr.
Stephan Kleuker
• Anmerkung: auch keine Löschung alleine durch CASCADETYPE.ALL
in Polygon, aber durch folgende Ergänzung
180
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
181
Anfrageausführung
Einfache Beispiele
• Setzt ordentliches toString() voraus
• ist nicht typsicher
Vorbemerkung: In FROM-Zeilen stehen Klassen und
Attributnamen; bei diesen muss Groß- und Kleinschreibung
beachtet werden!
public void anfragen(String ql)
ql) {
try {
Query query = em.createQuery(ql);
em.createQuery(ql);
Collection erg = query.getResultList();
query.getResultList();
for (Iterator it = erg.iterator();
erg.iterator(); it.hasNext();)
it.hasNext();) {
System.out.println(it.next());
System.out.println(it.next());
}
} catch (Exception
(Exception e) {
System.out.println("Anfrage gescheitert: "
+ e.getMessage());
e.getMessage());
}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
SELECT p FROM Projekt p
• Projekt Bonitaet (98304) [Konten Historie Raten ]
• Projekt Bremse (98305) [Display Sensoren Fusion ]
Direkter Zugriff auf Attribute
SELECT p.name FROM Projekt p
• Bonitaet
• Bremse
182
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
Nutzung mehrwertiger Relationen (1/2)
Nutzung mehrwertiger Relationen (2/2)
SELECT p.auftraege FROM Projekt p
• Aufgabe Raten (32769) durch Ivan als Java
• Aufgabe Konten (32770) durch Fatma als Cobol
• Aufgabe Historie (32772) durch Urs als Java
• Aufgabe Sensoren (32768) durch Ivan als C
• Aufgabe Display (32771) durch Fatma als Java
• Aufgabe Fusion (32773) durch Ivan als C
SELECT pa.bearbeiter FROM Projektauftrag pa
1: Ivan Auftraege=[
Auftraege=[ Sensoren Fusion Raten ]
Rollen=[ C Java ]
1: Ivan Auftraege=[
Auftraege=[ Sensoren Fusion Raten ]
Rollen=[ C Java ]
1: Ivan Auftraege=[
Auftraege=[ Sensoren Fusion Raten ]
Rollen=[ C Java ]
3: Fatma Auftraege=[
Auftraege=[ Konten Display ]
Rollen=[ Cobol Java ]
3: Fatma Auftraege=[
Auftraege=[ Konten Display ]
Rollen=[ Cobol Java ]
4: Urs Auftraege=[
Auftraege=[ Historie ]
Rollen=[ Java ]
Nicht erlaubt:
SELECT p.auftraege.bearbeiter FROM Projekt p
• An exception occurred while creating a query in EntityManager:
EntityManager:
Exception Description: Error compiling the query [SELECT
p.auftraege.bearbeiter FROM Projekt p], line 1, column 9:
invalid navigation expression [p.auftraege.bearbeiter
[p.auftraege.bearbeiter],
p.auftraege.bearbeiter], cannot
navigate collection valued association field [auftraege
[auftraege].
auftraege].
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
184
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
183
185
Neu zusammengesetzte Ergebnisse
Ausgabe mit detaillierterer Analyse
public void anfragen2(String ql)
ql) {
System.out.println(ql);
System.out.println(ql);
try {
Query query = em.createQuery(ql);
em.createQuery(ql);
Collection erg = query.getResultList();
query.getResultList();
for (Iterator it = erg.iterator();
erg.iterator(); it.hasNext();)
it.hasNext();) {
Object o = it.next();
it.next();
System.out.println(o+"
System.out.println(o+" :: "+ o.getClass().getName());
o.getClass().getName());
if(o.getClass().getName().equals("[Ljava.lang.Object;")){
if(o.getClass().getName().equals("[Ljava.lang.Object;")){
Object oa[]=
oa[]= (Object
(Object[])
Object[]) o;
for(int i=0;i<oa.length;i++)
System.out.println("
System.out.println(" "+oa[i
"+oa[i]);
oa[i]);
}
}
} catch (Exception
(Exception e) {
System.out.println("Anfragefehler:
System.out.println("Anfragefehler: " + e.getMessage());
e.getMessage());
}
}
• Folgende Folie zeigt Details der Ausgabe
SELECT r.name,
r.name, r.tagessatz FROM Rolle r
• [Ljava.lang.Object;@1db5ec :: [Ljava.lang.Object
[Ljava.lang.Object;
Ljava.lang.Object;
C
50
• [Ljava.lang.Object;@92b1a1 :: [Ljava.lang.Object
[Ljava.lang.Object;
Ljava.lang.Object;
Java
60
• [Ljava.lang.Object;@cbf9bd :: [Ljava.lang.Object
[Ljava.lang.Object;
Ljava.lang.Object;
Cobol
70
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
186
JOIN-Varianten
Prof. Dr.
Stephan Kleuker
187
Vordefinierte Anfragen + typisches Problem
private static final String PROJEKT_VON=
"SELECT DISTINCT(p.name)
DISTINCT(p.name) "
+"FROM Projekt p JOIN p.auftraege pa "
+"WHERE pa.bearbeiter.name=
pa.bearbeiter.name= :mname
:mname";
mname";
public void projektVon(String name){
name){
Query query = em.createQuery(PROJEKT_VON).
em.createQuery(PROJEKT_VON).
setParameter("mname",
setParameter("mname", name);
name);
Collection erg = query.getResultList();
query.getResultList();
for (Iterator it = erg.iterator();
erg.iterator(); it.hasNext();)
it.hasNext();) {
System.out.println(it.next());
System.out.println(it.next());
}
}
SELECT m.name
FROM Projektauftrag pa,
pa, Mitarbeiter m
WHERE pa.bearbeiter=m
AND pa.titel='Sensoren'
pa.titel='Sensoren'
SELECT m.name
FROM Projektauftrag pa JOIN pa.bearbeiter m
WHERE pa.titel='Sensoren'
pa.titel='Sensoren'
• Ivan
SELECT DISTINCT(pa.bearbeiter.name)
DISTINCT(pa.bearbeiter.name)
FROM Projekt p JOIN p.auftraege pa
WHERE p.name='Bremse'
p.name='Bremse'
• Fatma
• Ivan
Komponentenbasierte SoftwareEntwicklung
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
projektVon("Urs'
projektVon("Urs' OR NOT(p.name='
NOT(p.name='bla
='bla')
bla') OR p.name='
p.name='bla
='bla")
bla")
• Bremse
• Bonitaet
188
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
189
echte Standardkonformität?
•
•
•
•
Klassische SQL-Operatoren
gerade bei Anfragen häufig keine 100%-Kompatibilität
Hibernate meist etwas mächtiger als EclipseLink
Beispiel: Zeige alle C-Programmierer
geht:
SELECT m FROM Rolle r JOIN r.mitarbeiter m WHERE r.name='C
r.name='C‘
='C‘
[1: Ivan...
• geht nicht in EclipseLink:
SELECT m FROM Mitarbeiter m JOIN m.rollen r WHERE r.name='C'
r.name='C'
invalid navigation expression [r.name
[r.name],
r.name], cannot navigate
expression [r] of type [java.util.List
[java.util.List]
java.util.List] inside a query.
SELECT p.name,
p.name, COUNT(pa.titel)
COUNT(pa.titel)
FROM Projekt p JOIN p.auftraege pa
GROUP BY p.name
• [Ljava.lang.Object;@4a9a7d :: [Ljava.lang.Object
[Ljava.lang.Object;
Ljava.lang.Object;
Bonitaet
3
• [Ljava.lang.Object;@1e4a47e :: [Ljava.lang.Object
[Ljava.lang.Object;
Ljava.lang.Object;
Bremse
3
• Erinnerung AVG, SUM, MIN, MAX
• trotzdem: Immer sinnvoll Objektauswahlen in Anfragesprache
durchzuführen, nicht alle Objekte aus DB lutschen und dann
verarbeiten
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
190
Named Queries
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
Flexiblere Anfragenkonstruktion (Ausblick)
@NamedQueries({
NamedQueries({ //in Mitarbeiter (geht noch nicht in EclipseLink
@NamedQuery(name="Mitarbeiter.primaryKey",
NamedQuery(name="Mitarbeiter.primaryKey",
query="SELECT m FROM Mitarbeiter m WHERE m.minr=
m.minr= :minr
:minr"),
minr"),
@NamedQuery(name="Mitarbeiter.mitFaehigkeit",
NamedQuery(name="Mitarbeiter.mitFaehigkeit",
query="SELECT m FROM Rolle r JOIN r.mitarbeiter m"
+" WHERE r.name = :name
:name")
name")
})
// z. B. in Main mit lokalen Variablen minr und faehigkeit
Mitarbeiter m= em
.createNamedQuery("Mitarbeiter.primaryKey"
createNamedQuery("Mitarbeiter.primaryKey"
,Mitarbeiter.class)
Mitarbeiter.class)
.setParameter("minr",
setParameter("minr", minr)
minr)
.getSingleResult();
getSingleResult();
public void ooartigeAnfragekonstruktion(){
ooartigeAnfragekonstruktion(){
CriteriaBuilder qB = em.getCriteriaBuilder();
em.getCriteriaBuilder();
CriteriaQuery<Mitarbeiter>
CriteriaQuery<Mitarbeiter> cQ =
qB.createQuery(Mitarbeiter.class);
qB.createQuery(Mitarbeiter.class);
Root<Mitarbeiter>
Root<Mitarbeiter> mAlias=
mAlias= cQ.from(Mitarbeiter.class);
cQ.from(Mitarbeiter.class);
cQ.where(qB.notEqual(mAlias.get("name"),
cQ.where(qB.notEqual(mAlias.get("name"), "Urs"));
TypedQuery<Mitarbeiter>
TypedQuery<Mitarbeiter> tq=em.createQuery(cQ);
tq=em.createQuery(cQ);
for(Mitarbeiter m:tq.getResultList())
m:tq.getResultList())
System.out.println(m.getName());
System.out.println(m.getName());
}
for(
for( Mitarbeiter m:em
.createNamedQuery("Mitarbeiter.mitFaehigkeit"
createNamedQuery("Mitarbeiter.mitFaehigkeit"
,Mitarbeiter.class)
Mitarbeiter.class)
.setParameter("name",
setParameter("name", faehigkeit)
faehigkeit)
.getResultList())
getResultList())
System.out.println(m);
System.out.println(m);
Komponentenbasierte SoftwareEntwicklung
191
Prof. Dr.
Stephan Kleuker
Ivan
Fatma
Heinz
192
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
193
Vererbung – eine Tabelle (1/3)
Vererbung – eine Tabelle (2/3)
public static void main(String[]
main(String[] args)
args) {
EntityManagerFactory eMF =Persistence.
Persistence.
createEntityManagerFactory("JPAVererbungPU");
createEntityManagerFactory("JPAVererbungPU");
EntityManager em=eMF.createEntityManager();
em=eMF.createEntityManager();
em.getTransaction().begin();
em.getTransaction().begin();
em.persist(new Buch("JPA",
Buch("JPA", 2, 39.99f));
em.persist(new Produkt("Maus",
Produkt("Maus", 4, 7.99f));
em.persist(new Lebensmittel("Tofu",
Lebensmittel("Tofu", 7, 0.69f,new Date()));
em.getTransaction().commit();
em.getTransaction().commit();
for(Produkt p:(List<Produkt>)em.
p:(List<Produkt>)em.
createQuery("SELECT p FROM Produkt p").getResultList())
p").getResultList())
System.out.println(p);
System.out.println(p);
}
1:JPA Menge:2 Preis:39.99 Buch
2:Maus Menge:4 Preis:7.99
3:Tofu Menge:7 Preis:0.69 Verfall:Thu Oct 15 16:22:14 CEST 2009
@Entity
public class Produkt implements Serializable {
@Id @GeneratedValue private int prnr;
private String name;
private int lagermenge;
private float preis;
@Version private int version;
...}
@Entity
public class Lebensmittel extends Produkt implements
Serializable{
@Temporal(javax.persistence.TemporalType.DATE)
private Date verfallsdatum;
...}
@Entity
public class Buch extends Produkt{
Komponentenbasierte SoftwareEntwicklung
...}
Prof. Dr.
Stephan Kleuker
194
Vererbung – eine Tabelle (3/3)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
195
Vererbung – getrennte verknüpfte Tabellen
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
Inheritance(strategy=InheritanceType.JOINED)
public class Produkt implements Serializable {
SELECT * FROM Produkt
...}
SELECT * FROM Produkt
• Abbildung in eine Tabelle ist Default-Einstellung
• Ansatz meist am performantesten
• (float ungeeignet für Geldbeträge)
SELECT * FROM Lebensmittel
SELECT * FROM Buch
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
196
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
197
Vererbung – getrennte Tabellen
Software-Architektur
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Produkt implements Serializable {
...}
• sinnvoll:
Datenzugriff
kapseln
• typisch mehrere
DAO-Klassen
• entsteht
Datenzugriffskomponente
SELECT * FROM Produkt
SELECT * FROM Lebensmittel
SELECT * FROM Buch
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
198
Einbindung von Bean-Validation
199
@Entity
@MrXProgrammer(sprachen={"Java","E"},message="aktuelle Sprachen")
public class Mitarbeiter implements Serializable {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
GenerationType.AUTO)
private int minr;
minr;
@NotNull @Size(min=2, message="echter Nachname")
private String name;
name;
@OneToMany(mappedBy = "bearbeiter
"bearbeiter",
bearbeiter",
cascade = {CascadeType.PERSIST
{CascadeType.PERSIST,
CascadeType.PERSIST, CascadeType.MERGE})
CascadeType.MERGE})
@Basic(fetch = FetchType.EAGER)
FetchType.EAGER)
@Size(max=3, message="max 3 Auftraege")
Auftraege")
private Set<Projektauftrag>
Set<Projektauftrag> auftraege =
new HashSet<Projektauftrag>();
HashSet<Projektauftrag>();
@ManyToMany(mappedBy = "mitarbeiter
"mitarbeiter",
mitarbeiter",
cascade = {CascadeType.PERSIST
{CascadeType.PERSIST,
CascadeType.PERSIST, CascadeType.MERGE})
CascadeType.MERGE})
@Basic(fetch = FetchType.EAGER)
FetchType.EAGER)
@Size(max=2, message="max 2 Rollen")
private List<Rolle>
List<Rolle> rollen = new ArrayList<Rolle>();
ArrayList<Rolle>();
// ...
• Beispiel: externe Programmierernamen beginnen mit „X“,
müssen vorgegebene Sprachen können
Prof. Dr.
Stephan Kleuker
Prof. Dr.
Stephan Kleuker
Beispiel: JPA mit Bean-Validation (1/5)
• Annotationen wie @Column ermöglichen bereits Angabe
bestimmter Randbedingungen
• klarerer Ansatz: Trennung von Beschreibung des
Objektgraphen (wer mit wem) von Validierung
• Bean-Validation kann zusammen mit JPA genutzt werden;
Anwesenheit von Validatoren wird von EntityManagern
genutzt
• Ansatz: Wenn Daten in DB persistiert werden sollen,
werden alle Validierungsregeln geprüft (nicht eher); bei
Fehler wird Exception geworfen
• Zukunft: Standards werden noch enger verknüpft
Komponentenbasierte SoftwareEntwicklung
Komponentenbasierte SoftwareEntwicklung
200
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
201
Beispiel: JPA mit Bean-Validation (2/5)
Beispiel: JPA mit Bean-Validation (3/5)
public class MrXValidator implements
ConstraintValidator<MrXProgrammer,
ConstraintValidator<MrXProgrammer, Mitarbeiter> {
private String[] muss;
@Target({ElementType.TYPE})
Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MrXValidator.class)
MrXValidator.class)
@Documented
public @interface MrXProgrammer {
String message()
message() default "Moduleintrag kaputt";
Class<?>[]
Class<?>[] groups()
groups() default {};
Class<?
Class<? extends Payload>[]
Payload>[] payload()
payload() default {};
String[] sprachen() default {};
}
public void initialize(MrXProgrammer a) {
muss = a.sprachen();
a.sprachen();
}
public boolean isValid(Mitarbeiter t,
ConstraintValidatorContext cvc)
cvc) {
System.out.println("Pruefe fuer "+t.getName
"+t.getName());
t.getName());
if(!t.getName().startsWith("X"))
if(!t.getName().startsWith("X"))
return true;
true;
List<Rolle>
List<Rolle> rollen = t.getRollen();
t.getRollen();
for (Rolle r : rollen)
for (String s : muss)
if (r.getName().equals(s))
r.getName().equals(s))
return true;
true;
return false;
false;
}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
202
Beispiel: JPA mit Bean-Validation (4/5)
Prof. Dr.
Stephan Kleuker
Prof. Dr.
Stephan Kleuker
203
Beispiel: JPA mit Bean-Validation (5/5)
public void beispieldaten(){
beispieldaten(){
Mitarbeiter m1 = new Mitarbeiter("XUrs");
Mitarbeiter("XUrs");
Rolle[] r= { new Rolle("Java",
Rolle("Java", 60),new Rolle("C",
Rolle("C", 50)};
for(int i=0;i<r.length;i++)
m1.rolleHinzu(r[i]);
System.out.println(m1);
try {
em.getTransaction().begin();
em.getTransaction().begin();
em.persist(m1);
em.getTransaction().commit();
em.getTransaction().commit();
} catch (ConstraintViolationException
(ConstraintViolationException e) {
System.out.println(e.getMessage());
System.out.println(e.getMessage());
for(ConstraintViolation c:e.getConstraintViolations())
c:e.getConstraintViolations())
System.out.println(c.getMessage());
System.out.println(c.getMessage());
em.getTransaction().rollback();
em.getTransaction().rollback();
}
}
0: XUrs Auftraege=[
Auftraege=[ ]
Rollen=[
Rollen=[ Java C ]
Pruefe fuer XUrs
Komponentenbasierte SoftwareEntwicklung
Komponentenbasierte SoftwareEntwicklung
204
public void beispieldaten(){
beispieldaten(){
Mitarbeiter m1 = new Mitarbeiter("X");
Mitarbeiter("X");
Rolle[] r= { new Rolle("C++",
Rolle("C++", 60),new Rolle("C",
Rolle("C", 50),
new Rolle("Cobol",
Rolle("Cobol", 70)};
for(int i=0;i<r.length;i++) m1.rolleHinzu(r[i]);
System.out.println(m1);
try {
em.getTransaction().begin();
em.getTransaction().begin();
em.persist(m1);
em.getTransaction().commit();
em.getTransaction().commit();
} catch (ConstraintViolationException
(ConstraintViolationException e) {
for(ConstraintViolation c:e.getConstraintViolations())
c:e.getConstraintViolations())
System.out.println(c.getMessage());
System.out.println(c.getMessage());
em.getTransaction().rollback();
em.getTransaction().rollback();
0: X Auftraege=[
Auftraege=[ ]
}
Rollen=[
Rollen=
[ C++ C Cobol ]
}
Pruefe fuer X
echter Nachname
aktuelle Sprachen
max 2 Rollen
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
205
Interessante weitere Features in JPA
• in Anfrage-Sprache
– Funktionen auf Datentypen z. B.
SUBSTRING(String,Start,Ende)
– UPDATE und DELETE in Querys
• immer vor/nach Persistierung ausgeführte Methoden
• Compound Primary Keys , zusammengesetzte Schlüssel über
Hilfsklassen nutzbar
• Verschiedene Sperrvarianten
@Lob @Column(name="PIC
@Column(name="PIC")
Column(name="PIC")
private byte[]
byte[] picture;
picture;
@ManyToMany
@JoinTable(name="PROJEKTROLLEN",
JoinTable(name="PROJEKTROLLEN",
joinColumns=@JoinColumn(name=„
joinColumns=@JoinColumn(name=„ROLLEN_ID"),
ROLLEN_ID"),
inverseJoinColumns=@JoinColumn(name="PROJ_ID"))
inverseJoinColumns=@JoinColumn(name="PROJ_ID"))
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
206
Herunterladen