3. Java Persistence API (JPA)

Werbung
3. Java Persistence API (JPA)
•
•
•
•
•
•
•
•
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
113
Literatur
• JSR 338: JavaTM Persistence 2.1
http://jcp.org/en/jsr/detail?id=338 (JavaPersistence.pdf ist
der Standard; trotzdem sehr gut lesbar!)
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
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
114
Klassische Wege zur Verbindung SW und DB
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)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
115
Motivation
• Relationale Datenbanken sind das Mittel zur Persistierung
• Objekte müssen damit in Datenbanken gespeichert und
gefunden werden
• JDBC bietet alle Möglichkeiten; zur Vereinfachung wurden
oft individuelle Abstraktionen darauf gesetzt
• Object Relational Mapper (ORM) standen in Konkurrenz,
wurden dann mit JPA auf gemeinsamen Standard gesetzt
• Ziel 1: Aus Klassenmodell nahtlos Tabellen generieren und
nutzen
• Ziel 2: Einfache Anbindung existierender Datenbanken an
OO-Software
• Hinweis: JPA in Java SE und Java EE nutzbar
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
116
Erinnerung: Relationale Datenbanken
• Relationen (Tabellen) beschreiben einfache Entitäten
• Relationen beschreiben Verknüpfungen zwischen Entitäten
(-> Fremdschlüssel)
•
•
•
•
•
Modellierung mit ER-Diagramm
Einfache Übersetzung in Tabellen
Effizienter Zugriff mit SQL
ACID-Transaktionen
Zugriff von Java mit JDBC
• Wir nutzen JavaDB (Apache Derby)
http://www.oracle.com/technetwork/java/javadb/overview
/index.html
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
117
Beispiel (1/7)
package jpa20beispiel1;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Mitarbeiter {
@Id private int minr; // Primaerschluessel
private String name;
public Mitarbeiter(){} //parameterloser Konstruktor benötigt
public Mitarbeiter(int minr, String name) { //erlaubt
this.minr = minr;
this.name = name;
}
public
public
public
public
int getMinr() {return minr;}
void setMinr(int minr) {this.minr = minr;}
String getName() {return name;}
void setName(String name) {this.name = name;}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
118
Beispiel (2/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 version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="JPA20Beispiel1PU"
transaction-type="RESOURCE_LOCAL">
<provider>
org.eclipse.persistence.jpa.PersistenceProvider
</provider>
<class>jpa20beispiel1.Mitarbeiter</class>
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
119
Beispiel (3/7)
<properties>
<property name="javax.persistence.jdbc.url"
value="jdbc:derby://localhost:1527/Spielerei"/>
<property name="javax.persistence.jdbc.password"
value="kleuker"/>
<property name="javax.persistence.jdbc.driver"
value="org.apache.derby.jdbc.ClientDriver"/>
<property name="javax.persistence.jdbc.user"
value="kleuker"/>
<property name="eclipselink.ddl-generation"
value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
120
Beispiel (4/7)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
121
Beispiel (5/7)
package jpa20beispiel1;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class Main {
private EntityManagerFactory emf = Persistence.
createEntityManagerFactory("JPA20Beispiel1PU");
private EntityManager em = emf.createEntityManager();
public void beispieldaten() {
String namen[] = {"Egon", "Erwin", "Ute", "Aische"};
em.getTransaction().begin();
for (int i=0; i<namen.length; i++)
em.persist(new Mitarbeiter(i,namen[i]));
em.getTransaction().commit();
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
122
Beispiel (6/7)
public void datenZeigen() {
for (Mitarbeiter m : em.createQuery(
"SELECT m FROM Mitarbeiter m",Mitarbeiter.class)
.getResultList()) {
System.out.println(m.getMinr() + ": " + m.getName());
}
}
public void schliessen() {
if (em != null && em.isOpen()) {em.close();}
if (emf != null && emf.isOpen()) {emf.close();}
}
1: Egon
public static void main(String[] args) {
Main m = new Main();
2: Erwin
m.beispieldaten();
4: Aische
m.datenZeigen();
3: Ute
m.schliessen();
}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
123
Beispiel (7/7)
• Falls keine Tabelle Mitarbeiter existiert, wird diese angelegt
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
124
Suchen und Bearbeiten von einzelnen Objekten
public void namenAendern(){
int eingabe=-1;
while(eingabe!=0) {
System.out.print("Welche Nummer (Ende mit 0): ");
eingabe=new Scanner(System.in).nextInt();
Mitarbeiter m = em.find(Mitarbeiter.class, eingabe);
if(m == null)
System.out.println("Witzbold");
else {
System.out.print("Neuer Name (alt:"+m.getName()+"): ");
String name=new Scanner(System.in).next();
EntityTransaction tr = em.getTransaction();
tr.begin();
m.setName(name);
// em.merge(m); steht hier üblicherweise, geht ohne
tr.commit();
}
}
}Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
125
Zentrale Klassen
• Aufbau der Verbindung
EntityManagerFactory emf = Persistence.
createEntityManagerFactory("JPA20Beispiel1PU");
– Einrichtung sehr aufwändig, selten neu erstellen
• Einrichtung der Verbindung für den Nutzer
EntityManager em = emf.createEntityManager();
– kostet Zeit, längere Zeit nutzbar, ob mehrfach erzeugen
oder nicht hängt von Rahmenbedingungen ab
• Nutzung einer Transaktion
EntityTransaction tr = em.getTransaction();
– kurzfristig nutzen: Daten vorbereiten, dann DB-Zugriff,
dann schließen (zum Lesen nicht benötigt)
• letztendlich 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
126
Entity ist POJO
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
• keine weiteren Einschränkungen
– beliebige weitere Methoden
– Vererbung (auch Entity von Nicht-Entity [aufwändig])
– Nutzung abstrakte Klassen
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
127
Id und equals
• Java regelt Gleichheit normalerweise über equals() und
hashCode()
• nun existiert die Objekt-Id, die Objekte eindeutig
identifizieren soll
• ein sinnvoller Ansatz: in equals() und hashCode() nur diese
Id nutzen
• Konsequenz: keine inhaltliche Prüfung, sonstige inhaltliche
Gleichheit wird nicht berücksichtigt
• beachten: das Objekt erhält seine Id erst mit der
Persistierung
• Vergleich von persistierten und nicht persistierten Objekten
dann nicht mehr möglich (auch von unpersistierten nicht)
• Fazit: Es gibt keine optimale Lösung für Gleichheit
Prof. Dr.
Stephan Kleuker
128
Primary Key
Typisch: Primary Key wird erzeugt
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int 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
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
129
Persistierbare Typen/Klassen
• 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)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
130
Persistent Fields / Properties
• Persistent Fields
– @Id private int minr;
– Exemplarvariablen direkt annotiert
– Zugriff des Persistence-Frameworks direkt auf Variablen
– Vorteil: Variable und Annotation stehen direkt
zusammen
• Persistent Properties
– @Id public int getMinr{ return 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
Prof. Dr.
Stephan Kleuker
131
Annotationen zur Flexibilisierung / Ergänzung
@Entity
@Table(name="Chef")
public class Mitarbeiter implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int minr;
@Column( name="Leiter", nullable=false
,updatable=true, unique=true)
private String name;
...
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
132
PersistenceContext
•
•
•
•
•
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
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)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
133
Beispiel für Cache (1/2)
@Entity
public class Mitarbeiter implements Serializable {
@Id @GeneratedValue
private int minr;
@Column(length=2) //maximal zwei Zeichen
private String name;
public Mitarbeiter() {
} //parameterloser Konstruktor benötigt
public Mitarbeiter(String name) {
this.name = name;
}
// get- und set-Methoden weggelassen
@Override public String toString(){
return name+"("+minr+")";
}
Komponentenbasierte
Software}
Entwicklung
Prof. Dr.
Stephan Kleuker
134
Beispiel für Cache (2/2)
public static void main(String[] args) {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("JPACachePU");
EntityManager em=emf.createEntityManager();
em.getTransaction().begin();
em.persist(new Mitarbeiter("ET"));
em.persist(new Mitarbeiter("JFK"));
for(int i=1;i<3;i++)
System.out.println(em.find(Mitarbeiter.class,i));
em.getTransaction().commit();
em.close();
} ET(1)
}
JFK(2)
[EL Warning]: 2010-03-28 13:12:57.868-UnitOfWork(20290587)also
Exception [EclipseLink-4002]:
kein
org.eclipse.persistence.exceptions.DatabaseException
Eintrag Internal Exception: java.sql.SQLDataException: Bei dem
in der
Versuch, VARCHAR 'JFK' auf die Länge 2 zu kürzen, ist
DB
ein Abschneidefehler aufgetreten.
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
135
Lebenslauf eines Entity-Objekts
NEW -> merge() führt evtl. zur Mehrfachobjekterzeugung
refresh() nur, wenn vorher persistiert
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
136
Lebenslaufanalyse (1/13) - Beispielentität
@Entity
public class Mitarbeiter2 implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id; // mit get- und set-Methode
private String name; // mit get- und set-Methode
public Mitarbeiter2(){}
public Mitarbeiter2(int val, String name){
this.id = val;
this.name = name;
}
@Override
public String toString() {
return "["+this.id+": "+this.name+"]";
}
// ...
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
137
Lebenslaufanalyse (2/13) – Umgebung (nichts Neues)
public class Main2 {
private EntityManagerFactory emf = Persistence
.createEntityManagerFactory("JPASpielereiPU2");
private EntityManager em = emf.createEntityManager();
// ...
persistence.xml:
<persistence-unit name="JPASpielereiPU2"
transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider
</provider>
<class>jpaspielerei.Mitarbeiter2</class>
<properties>
<property name="javax.persistence.jdbc.url"
value="jdbc:derby://localhost:1527/Spielerei"/>
<property name="javax.persistence.jdbc.password"
value="kleuker"/>
...
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
138
Lebenslaufanalyse (3/13) - Analysemethode
public void beinhaltet(int nr, Mitarbeiter2 ma) {
System.out.println(nr + ": " + ma + ": "+ this.em.contains(ma));
Map<String, Object> map = this.em.getProperties();
System.out.println("-----------------");
try (Connection con = DriverManager
.getConnection((String)map.get("javax.persistence.jdbc.url"),
(String)map.get("javax.persistence.jdbc.user"),
(String)map.get("javax.persistence.jdbc.password"))) {
Statement stmt = con.createStatement();
ResultSet rs = stmt
.executeQuery("SELECT * FROM Mitarbeiter2");
while (rs.next()) {
System.out.print(rs.getString("id")
+ ": " + rs.getString("name")+"\n");
}
} catch(Exception e){
System.out.println("Datenbankfehler: "+e.getMessage());
}
System.out.println("-----------------\n");
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
139
Lebenslaufanalyse (4/13) - Beispielschritte
public void lifecycle1() {
Mitarbeiter2 m = new Mitarbeiter2(1, "ich");
beinhaltet(0, m);
m
0: [1: ich]: false
---------------------------------
1
ich
em.getTransaction().begin();
em.persist(m);
beinhaltet(1, m);
m
Komponentenbasierte SoftwareEntwicklung
1
ich
Prof. Dr.
Stephan Kleuker
id
name
id
name
id
name
1
ich
PC
1
ich
em.getTransaction().commit();
beinhaltet(2, m);
m
PC
PC
140
Lebenslaufanalyse (5/13) - Beispielschritte
PC
m.setName("Deep Thought");
beinhaltet(3, m);
1
Deep Thought
m
em.refresh(m);
beinhaltet(4, m);
m.setName("Calculon");
beinhaltet(5, m);
name
1
ich
id
name
1
ich
id
name
1
ich
id
name
1
ich
PC
1
ich
m
PC
1
Calculon
m
Mitarbeiter2 m2 = em.merge(m);
beinhaltet(6, m2);
m
m2
Komponentenbasierte SoftwareEntwicklung
id
PC
1
Calculon
Prof. Dr.
Stephan Kleuker
141
Lebenslaufanalyse (6/13) - Beispielschritte
em.persist(m);
beinhaltet(7, m);
PC
1
Calculon
m
m2
em.getTransaction().begin();
beinhaltet(8, m);
m
m2
em.getTransaction().commit();
beinhaltet(9, m);
m
m2
id
name
1
ich
id
name
1
ich
id
name
1
Calculon
PC
1
Calculon
PC
1
Calculon
• persist(.) gibt nur an, dass ein Objekt gespeichert werden
soll, dies ist außerhalb von Transaktionen möglich (man
sollte aber einheitlichen Stil nutzen und dies vermeiden)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
142
Lebenslaufanalyse (7/13) - Beispielschritte
m.setName("Linguo");
em.getTransaction().begin();
em.getTransaction().commit();
beinhaltet(10, m);
em.clear();
beinhaltet(11, m);
m
m2
PC
1
Linguo
id
name
1
Linguo
id
name
1
Linguo
PC
m
m2
1
Linguo
• Sollen in einer Transaktion zwischenzeitlich alle SQL-Befehle
ausgeführt, dann weitere Aktionen und dann ein commit
ausgeführt werden, wird em.flush() genutzt
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
143
Lebenslaufanalyse (8/13) - Beispielschritte
m2 = em.find(Mitarbeiter2.class, 1);
System.out.println("m == m2 : "+(m == m2));
// false
m.setName("HAL");
m2.setName("EVE");
beinhaltet(12, m);
beinhaltet(13, m2);
PC
m
1
HAL
m2
m = em.merge(m);
beinhaltet(14, m);
beinhaltet(15, m2);
1
EVE
id
name
1
Linguo
id
name
1
Linguo
PC
m
m2
1
HAL
System.out.println("m == m2 :"+(m==m2)); // true
• merge(.) aktualisiert/vermischt lokales PC-Objekt mit Daten
des übergebenen Objektes und gibt PC-Objekt zurück
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
144
Lebenslaufanalyse (9/13) - Fazit
• man sollte wissen, welche Objekte sich im Persistence
Context (PC) jeweils befinden
• man braucht definitiv nicht alle gezeigten Varianten
• typischer Ansatz
– sehr kurze Transaktionen
– persist(.) und remove(.) finden in den Transaktionen
statt
– merge(.) seltener, aber notwendig, um lokale
Änderungen in PC zu übertragen
– wenn man aufräumen will clear() explizit nutzen
– weitere Befehle nur nutzen, wenn unbedingt notwendig
• vom Ansatz abweichen, wenn viele Transaktionen SW
ausbremsen (dann wird es kompliziert)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
145
Lebenslaufanalyse (10/13) – Zweites Beispiel
public void lifecycle2() {
Mitarbeiter2 m = new Mitarbeiter2(1, "ich");
Mitarbeiter2 m2 = new Mitarbeiter2(1, "Nono");
try {
em.getTransaction().begin();
em.persist(m);
beinhaltet(1, m);
PC
m2
1
Nono
m
1
ich
em.persist(m2);
beinhaltet(2, m2);
id
name
id
name
PC
m
m2
1
ich
1
Nono
em.getTransaction().commit();
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
146
Lebenslaufanalyse (11/13) – Zweites Beispiel
} catch (Exception e) {
System.out.println(e.getMessage());
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
System.out.println("rolled back");
}
}
// liefert nur Internal Exception:
// java.sql.SQLIntegrityConstraintViolationException:
beinhaltet(3, m);
PC
m
1
ich
m2
id
1
Nono
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
name
147
Lebenslaufanalyse (12/13) – Zweites Beispiel
em.persist(m);
em.getTransaction().begin();
em.getTransaction().commit();
beinhaltet(4, m);
m2
1
Nono
PC
1
ich
m
em.persist(m2);
beinhaltet(5, m2);
em.refresh(m2);
beinhaltet(6, m2);
id
name
1
ich
id
name
1
ich
id
name
1
ich
PC
m
m2
1
ich
1
Nono
PC
m
m2
1
ich
1
ich
em.getTransaction().begin();
em.getTransaction().commit();
// java.sql.SQLIntegrityConstraintViolationException
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
148
Lebenslaufanalyse (13/13) – SQL-Befehle sichtbar
für Eclipselink in persistence.xml:
<property name="eclipselink.logging.level" value="SEVERE"/>
<property name="eclipselink.logging.level" value=“FINE"/>
<property name="eclipselink.logging.level" value=“FINEST"/>
allgemein ab JPA 2.1, SQL-Skripte erzeugen
<property
name="javax.persistence.schema-generation.scripts.action"
value="drop-and-create"/>
<property
name="javax.persistence.schema-generation.scripts.drop-target"
value="mydrop.ddl"/>
<property
name="javax.persistence.schema-generation.scripts.create-target"
value="mycreate.ddl"/>
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
149
Sauberes Persistieren
• Auslagerung der Persistierung in eine Methode mit sauberer
Exception-Behandlung
• Anmerkung: man muss nicht immer em.close() machen
public void persist(Object object) {
em.getTransaction().begin();
try {
em.persist(object);
em.getTransaction().commit();
} catch (Exception e) {
if (em.getTransaction().isActive())
em.getTransaction().rollback();
throw e; // oder neue Exception
} finally {
em.close();
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
150
oftmals sinnvoll: DB-Zugriffe zentralisieren (1/3)
• neue Schicht: eine oder mehrere Klassen, die JPA-Zugriffe
durchführen
• für Entwickler einfacher: ggfls. nicht im Detail über
Persistence Context nachdenken
public class Persistierer {
private EntityManagerFactory emf;
private EntityManager em;
public Persistierer(String persistence) {
emf = Persistence
.createEntityManagerFactory(persistence);
em = emf.createEntityManager();
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
151
oftmals sinnvoll: DB-Zugriffe zentralisieren (2/3)
public void persist(Object ob) { public Object merge(Object ob) {
em.getTransaction().begin();
Object erg = null;
try {
em.getTransaction().begin();
em.persist(ob);
try {
em.getTransaction()
erg = em.merge(ob);
.commit();
em.getTransaction()
} catch (Exception e) {
.commit();
// evtl Logging
} catch (Exception e) {
throw e;
throw e;
}
}
}
return erg;
// remove(Object ob) analog
}
• Exception-Handling muss im Projekt geklärt werden
• JPA-Exception müssen weder gefangen noch deklariert werden
• meist nach oben reichen (und dabei noch umwandeln)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
152
oftmals sinnvoll: DB-Zugriffe zentralisieren (3/3)
public Mitarbeiter findMitarbeiter(long id) {
return em.find(Mitarbeiter.class, id);
}
public List<Mitarbeiter> findAllMitarbeiter() {
return em.createQuery(
"SELECT m FROM Mitarbeiter m",Mitarbeiter.class)
.getResultList();
}
public void schliessen() {
if (em != null && em.isOpen()) {
em.close();
}
if (emf != null && emf.isOpen()) {
emf.close();
}
Komponentenbasierte
SoftwareProf. Dr.
}
Entwicklung
Stephan Kleuker
}
153
Klasse oder Tabelle?
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
154
Generelle JEE-Regel
Convention over Configuration
• bedeutet: wenn nichts angegeben wird, wird ein DefaultWert genutzt
• Default-Werte sind zwar sinnvoll, sollte man aber kennen
• Erinnerung: Java-Inkonsistenz
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
155
Einschub: XML-Konfiguration
• 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
• Auch möglich: XML und Annotationen; dabei „schlägt“ XML
die Annotationen
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
156
Kardinalitäten in JPA
A
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
Prof. Dr.
Stephan Kleuker
157
Zielsoftware Sprinter
• Verwaltung von Sprints, in denen BacklogElemente
abgearbeitet werden
• Zu jedem BacklogElement gibt es einen Menge von
Mitarbeiten (Teilaufgaben)
• Jeder Mitarbeiter kann an mehreren Mitarbeiten mitwirken
• Das Werkzeug soll Informationen über den aktuellen Stand
von BacklogElementen und Sprints ausgeben
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
158
Hintergrund
• Im agilen Vorgehensmodell wird Projekt in mehrere Sprints
aufgeteilt (diese Aufgaben stehen im Product-Backlog)
• Arbeiten in einem Sprint werden Backlog-Elemente genannt
• Backlog-Elemente werden von Mitarbeitern abgearbeitet
Prozess:
1. aus Sprint-Länge und Anzahl der Mitarbeiter wird maximal
verfügbare Stundenzahl berechnet
2. Festlegung der (Sprint-)Backlog-Elemente, die im Sprint zu
erledigen sind, mit Aufwandsschätzung durch Mitarbeiter
3. Festlegung der durchführenden Mitarbeiter mit Arbeitsanteil
4. ggfls. Anpassung der für Sprint zur Verfügung stehenden
Stundenanzahl
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
159
Ausschnitt
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
160
Datenmodell
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
161
Umsetzungen von Beziehungen
• Datenbanken kennen drei wesentliche Kardinalitäten für
Beziehungen 1-1, 1-N , M-N
• diese werden bei Objektvariablen direkt an die Beziehung
geschrieben
• @OneToOne: Zuordnung zu (maximal) einem Objekt
• @OneToMany: Zuordnung von beliebig vielen Objekten
(steht typischerweise bei Collections)
• @ManyToOne: Zuordnung zu einem Objekt (kann selbst
mehrfach zugeordnet sein, steht, wenn benötigt, auf
anderer Seite der @OneToMany-Beziehung
• @ManyToMany: Zuordnung von beliebig vielen Objekten,
die auch wahrscheinlich in die Rückrichtung genutzt wird
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
162
Erinnerung: Umsetzung 1:N bei Datenbanken (1/3)
• Zu jedem BacklogElement gehören beliebig viele Mitarbeiten
• Jede Mitarbeit gehört zu genau einem BacklogElement
• Standardumsetzung
– pro Entität eine Tabelle
– in Tabelle Mitarbeit wird die id des zugehörigen
BacklogElements als Fremdschlüssel eingetragen
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
163
Erinnerung: Umsetzung 1:N bei Datenbanken (2/3)
• Erster Umsetzungsversuch
• BacklogElement hat eine Collection von Mitarbeiten
• kleines fachliches Problem: Mitarbeit kennt nicht
zugehöriges BacklogElement
• großes fachliches Problem: eine Mitarbeit kann immer noch
zu mehreren unterschiedlichen BacklogElementen gehören
• konsequente automatische JPA-Umsetzung: dritte Tabelle
mit Fremdschlüsseln verknüpfter Objekte (wie M-N)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
164
Erinnerung: Umsetzung 1:N bei Datenbanken (3/3)
• Umsetzung (im Wesentlichen Identisch zum DB-Ansatz)
• Bidirektionale Beziehung (Nachteil beide Richtungen immer
pflegen)
• immer noch könnte ein Mitarbeit-Objekt zu einem anderen
BacklogElement-Objekt gehören, als in dessen Collection es ist
• Lösung durch mappedBy-Atribut Verknüpfung sichern
@OneToMany(mappedBy = "backlogElement")
private List<Mitarbeit> mitarbeiten;
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
165
Wichtige Annotationen in Großprojekten
@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)
private Set<Mitarbeiter> mitarbeiter
• EAGER: Alle Daten des Attributs werden bei Objektnutzung
sofort in Hauptspeicher geladen
• 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
166
Umsetzung des Beispiels (1/9): Klassendiagramm
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
167
Umsetzung des Beispiels (2/9): BacklogElement
@Entity
public class BacklogElement implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Version
private int version;
@Length(min=1)
private String titel;
private int geplanterAufwand;
@OneToMany( mappedBy = "backlogElement"
, fetch = FetchType.EAGER)
private List<Mitarbeit> mitarbeiten;
Werte als Strings, nicht
@Enumerated(EnumType.STRING)
Ordnungsnummern, speichern
private Phase phase;
(besser bei Änderungen)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
168
Umsetzung des Beispiels (3/9): Enumeration
public enum Phase {
ZU_BEARBEITEN("zu bearbeiten"),
VORBEREITUNG_IN_ARBEIT("Vorbereitung in Arbeit"),
VORBEREITUNG_ABGESCHLOSSEN("Vorbereitung abgeschlossen"),
ENTWICKLUNG_IN_ARBEIT("Entwicklung in Arbeit"),
ENTWICKLUNG_ABGESCHLOSSEN("Entwicklung abgeschlossen"),
TEST_IN_ARBEIT("Test in Arbeit"),
TEST_ABGESCHLOSSEN("Test abgeschlossen"),
ABNAHME_IN_ARBEIT("Abnahme in Arbeit"),
ABNAHME_ABGESCHLOSSEN("Abnahme abgeschlossen"),
FERTIG("fertig");
private String text;
Phase(String text){
this.text = text;
}
@Override public String toString() { return this.text; }
}
Komponentenbasierte
SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
169
Umsetzung des Beispiels (4/9): Mitarbeit
@Entity
public class Mitarbeit implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Version
private int version;
private int geplanteStunden;
private int verbrauchteStunden;
private int fertigstellungsgrad;
private String taetigkeit;
@ManyToOne
private Mitarbeiter mitarbeiter;
@ManyToOne
private BacklogElement backlogElement;
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
170
Umsetzung des Beispiels (5/9): Mitarbeiter
@Entity
public class Mitarbeiter implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Version
private int version;
@Column(unique = true)
private int minr;
private String name;
@OneToMany(mappedBy = "mitarbeiter"
, fetch = FetchType.EAGER)
private List<Mitarbeit> mitarbeiten;
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
171
Umsetzung des Beispiels (6/9): Datenerzeugung 1/2
private String[] backlog = ...;
private String[] aufgaben = ...;
public void zuordnen() {
this.pers = new Persistierer("SprinterJSE");
for (String bl : this.backlog) {
BacklogElement b = new BacklogElement(bl, 42);
pers.persist(b);
this.backlogIDs.add(b.getId());
}
int minr = 1000; //+ (int)(Math.random()*99000);
for (String n : this.namen) {
Mitarbeiter m = new Mitarbeiter(minr++, n);
pers.persist(m);
this.mitarbeiterIDs.add(m.getId());
}
Komponentenbasierte SoftwareProf. Dr.
Entwicklung
Stephan Kleuker
172
Umsetzung des Beispiels (7/9): Datenerzeugung 2/2
int pos = 0;
for (String a : this.aufgaben) {
Mitarbeit m = new Mitarbeit(20, 12, 70, a);
pers.persist(m);
BacklogElement b = pers
.findBacklogElement(this.backlogIDs.get(pos / 2));
b.addMitarbeit(m);
Auswahl eines passendes Objekt
pers.merge(b);
anhand einer vorhandenen Id
m.setBacklogElement(b);
(Details uninteressant)
Mitarbeiter ma = pers
.findMitarbeiter(this.mitarbeiterIDs
.get(pos * 7 % this.mitarbeiterIDs.size()));
ma.addMitarbeit(m);
pers.merge(ma);
m.setMitarbeiter(ma);
// pers.merge(m);
pos++;
Komponentenbasierte SoftwareProf. Dr.
173
} Entwicklung
Stephan Kleuker
Umsetzung des Beispiels (8/9): Mitarbeit-Erzeugung
sicherer / konservativer Ansatz
• Mitarbeit anlegen und persistieren
• Mitarbeit bei BacklogElement hinzufügen
• BacklogElement mergen
• BacklogElement bei Mitarbeit hinzufügen
• Mitarbeit bei Mitarbeiter hinzufügen
• Mitarbeiter mergen
• Mitarbeiter bei Mitarbeit hinzufügen
• Mitarbeit mergen ist hier unnötig
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
174
Umsetzung des Beispiels (9/9): Ergebnisanalyse
• Ansatz funktioniert
• Mitarbeit hat bereits Versionsnummer 3
• Versionsnummer, da angelegt und mit BacklogElement
sowie Mitarbeiter „aktualisiert“ (diese erkennen die
aktuelle Version nicht)
• Im Fall BacklogElement – Mitarbeit-Mitarbeiter kann auf
cascade = {CascadeType.PERSIST, CascadeType.MERGE}
verzichtet werden (ist hier so), muss es aber nicht
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
175
Löschen
• Löschen ohne CascadeType.remove verläuft wieder völlig
analog zu klassischen Datenbanken
• Löschen eines BacklogElements
public void removeBacklogElement(BacklogElement b) {
for (Mitarbeit m : b.getMitarbeiten()) {
Mitarbeiter ma = m.getMitarbeiter();
if (ma != null) {
ma.removeMitarbeit(m);
zugehörige Mitarbeiten
merge(ma);
bei Mitarbeiter löschen
}
remove(m);
}
remove(b);
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
176
Cascade-Varianten
@OneToMany(cascade={CascadeType.PERSIST, 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
transient instance - save the transient instance before flushing:
Projektauftrag
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
177
Weitere interessante Annotationen / Attribute
• Variable wird nicht persistiert
@Transient
• zur Nutzung von java.util.Date
@Temporal(TemporalType.DATE)
private Date starttermin;
• zur Sicherstellung der Reihenfolge
@OrderColumn(name="Ord")
private List<Mitarbeiter> mitarbeiter = new ArrayList<>();
• bei Existenzabhängigkeit, Löschen der abhängigen Objekte
@OneToMany(cascade = {CascadeType.ALL}, orphanRemoval=true)
• einfache Nutzung von Vererbung (drei Varianten)
@Inheritance(strategy=InheritanceType. SINGLE_TABLE)
@Inheritance(strategy=InheritanceType.JOINED)
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
178
Anfragen
• 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>
– Übersetzung: Wähle aus der Menge Projekt der
gemanageten Objekte die Elemente p mit Eigenschaft
<Bed>
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
179
Anfrageausführung in Persistierer
• Setzt ordentliches toString() voraus
• ist nicht typsicher
public void anfragen(String ql) {
try {
Query query = em.createQuery(ql);
Collection erg = query.getResultList();
for (Iterator it = erg.iterator(); it.hasNext();) {
System.out.println(it.next());
}
} catch (Exception e) {
System.out.println("Anfrage gescheitert: "
+ e.getMessage());
}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
180
Einfache Beispiele
Vorbemerkung: In FROM-Zeilen stehen Klassen und
Attributnamen; bei diesen muss Groß- und Kleinschreibung
beachtet werden!
SELECT b FROM BacklogElement b
(1) Klassenmodellierung
(2) CRUD-Umsetzung [9 Ergebnisse]
Direkter Zugriff auf Attribute
SELECT b.titel FROM BacklogElement b
Klassenmodellierung
CRUD-Umsetzung [9 Ergebnisse]
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
181
Nutzung mehrwertiger Relationen
SELECT b.mitarbeiten FROM BacklogElement b
Ergebnis wird
Mitarbeit [id=19, version=3, geplanteStunden=20,
geflattened
verbrauchteStunden=12, fertigstellungsgrad=70]
(vereinigt)
Mitarbeit [id=20, version=3, geplanteStunden=20,
verbrauchteStunden=12, fertigstellungsgrad=70] [18 Ergebnisse]
Nicht erlaubt:
SELECT b.mitarbeiten.mitarbeiter FROM BacklogElement b
An exception occurred while creating a query in EntityManager:
Exception Description: Problem compiling [SELECT
b.mitarbeiten.mitarbeiter FROM BacklogElement b].
[7, 32] The state field path 'b.mitarbeiten.mitarbeiter' cannot
be resolved to a valid type.
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
182
JOIN-Varianten
SELECT mi.name
FROM Mitarbeit ma, Mitarbeiter mi
WHERE ma.mitarbeiter = mi
AND ma.backlogElement.titel = 'Klassenmodellierung'
Homer Simpson
Edna Krabappel
bringt gleiches Ergebnis
wie vorher, ist aber
objektorientierter
SELECT mi.name
FROM Mitarbeit ma JOIN ma.mitarbeiter mi
WHERE ma.backlogElement.titel = 'Klassenmodellierung'
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
183
Neu zusammengesetzte Ergebnisse
SELECT b.titel, b.geplanterAufwand FROM BacklogElement b
[Ljava.lang.Object;@640334cb
[Ljava.lang.Object;@e7f2eb9 [9 Ergebnisse]
detaillierter mit nachfolgender Methode anfrage2
SELECT b.titel, b.geplanterAufwand FROM BacklogElement b
[Ljava.lang.Object;@59f31dd3 :: [Ljava.lang.Object;
Klassenmodellierung
42
[Ljava.lang.Object;@682b35c7 :: [Ljava.lang.Object;
CRUD-Umsetzung
42
[9 Ergebnisse]
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
184
Ausgabe mit detaillierterer Analyse
public void anfragen2(String ql) {
System.out.println(ql);
try {
Query query = em.createQuery(ql);
Collection erg = query.getResultList();
for (Iterator it = erg.iterator(); it.hasNext();) {
Object o = it.next();
System.out.println(o+" :: "+ o.getClass().getName());
if(o.getClass().getName().equals("[Ljava.lang.Object;")){
Object oa[]= (Object[]) o;
for(int i=0;i<oa.length;i++)
System.out.println(" "+oa[i]);
}
}
} catch (Exception e) {
System.out.println("Anfragefehler: " + e.getMessage());
}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
185
Vordefinierte Anfragen + typisches Problem
private final String backlogElementMit = "SELECT b.titel "
+ " FROM BacklogElement b JOIN b.mitarbeiten ma "
+ "
JOIN ma.mitarbeiter mi"
+ " WHERE mi.name = :name";
public void backlogElementeMit(String n) {
Query query = this.pers.getEm()
.createQuery(this.backlogElementMit)
.setParameter("name", n);
Collection erg = query.getResultList();
for (Iterator it = erg.iterator(); it.hasNext();) {
System.out.println(it.next());
}
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
186
echte Standardkonformität?
• gerade bei Anfragen häufig keine 100%-Kompatibilität
• trotzdem: Immer sinnvoll, Objektauswahlen in Anfragesprache
durchzuführen, nicht alle Objekte aus DB lesen und dann
verarbeiten
• JPQL unterstützt mittlerweile fast alle SQL-StandardOperatoren, selbst wenn diese aus OO-Sicht fragwürdig sind
– SUM AVG COUNT MIN MAX
– GROUP BY HAVING ORDER BY
– DISTINCT
– EXISTS
– ANY ALL
• trotzdem aufpassen: Fehler können andeuten, dass Detail
noch nicht offiziell unterstützt wird
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
187
Klassische SQL-Operatoren
SELECT b.titel, SUM(b.geplanterAufwand)
, SUM(ma.geplanteStunden)
FROM BacklogElement b JOIN b.mitarbeiten ma
GROUP BY b.titel
[Ljava.lang.Object;@64114e1a :: [Ljava.lang.Object;
Architektur
84
40
[Ljava.lang.Object;@2c1ec049 :: [Ljava.lang.Object;
Basisdienste
84
40
[9 Ergebnisse]
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
188
Named Queries (1/2)
@Entity
@NamedQueries({
@NamedQuery(name = "BacklogElement.findAll",
query = "SELECT o FROM BacklogElement o"),
@NamedQuery(name = "BacklogElement.findMitarbeit",
query = "SELECT DISTINCT m "
+ "
FROM BacklogElement b JOIN b.mitarbeiten m "
+ " WHERE b.id = :id "
+ "ORDER BY m.fertigstellungsgrad DESC")
})
public class BacklogElement implements Serializable {...
zentrales Ziel: alle Anfragen vorformuliert in Entity-Klassen; ein
Vorteil: Syntaxprüfung bereits bei Kompilierung
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
189
Named Queries (2/2)
• Nutzung: hier in Persistierer
public List<BacklogElement> findAllBacklogElement() {
return em.createNamedQuery("BacklogElement.findAll")
.getResultList();
}
public List<Mitarbeit> findAllBacklogElementMitarbeit(long id) {
return em.createNamedQuery("BacklogElement.findMitarbeit")
.setParameter("id", id)
.getResultList();
}
Anmerkung: einzelne Objekte per Id immer mit em.find() suchen,
da so automatisch im Persistence Context
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
190
Ungeordnete Daten (1/4)
@Entity
public class Punkt implements Serializable{
@Id @GeneratedValue
private int id;
private int x;
private int y;
@Version
private int version;
public Punkt(int x, int y) {
this.x = x;
this.y = y;
}
Problem: Auch
Listen werden
ungeordnet
gespeichert
public Punkt(){}
@Override
public String toString(){
return "["+x+","+y+"]";
}
// get- und set-Methoden für Exemplarvariablen
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
191
Ungeordnete Daten (2/4)
@Entity
public class Polygon implements Serializable{
@Id @GeneratedValue
private int id;
@OneToMany(cascade = {CascadeType.ALL})
private List<Punkt> punkte= new ArrayList<Punkt>();
@Version private int version;
public Polygon(){} //get und set für Exemplarvariablen
public void punkteHinzu(Punkt... pkte){
for(Punkt p:pkte)
punkte.add(p);
}
@Override public String toString(){
StringBuffer erg=new StringBuffer("<");
for(Punkt p:punkte)
erg.append(p.toString());
return erg.append(">").toString();
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
192
Ungeordnete Daten (3/4)
public class Main {
private EntityManagerFactory emf =
Persistence.createEntityManagerFactory("JPA20NeueFeaturesPU");
private EntityManager em = emf.createEntityManager();
public void objekteErzeugen(){
Punkt[] pkt={new Punkt(0,0), new Punkt(5,3),
new Punkt(3,3), new Punkt(3,0)};
em.getTransaction().begin();
for(Punkt p:pkt)
em.persist(p);
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.persist(p1);
em.persist(p2);
em.getTransaction().commit();
} Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
193
Ungeordnete Daten (4/4)
public void zeigePolygone(){
List<Polygon> pl = em.createQuery(
"SELECT p FROM Polygon p",Polygon.class).getResultList();
for(Polygon po:pl)
System.out.println(po);
}
public void schliessen() {
if (em != null && em.isOpen()) em.close();
if (emf != null && emf.isOpen()) emf.close();
}
public static void main(String[] args) {
Main m= new Main();
m.objekteErzeugen();
<[0,0][5,3][3,3]>
m.zeigePolygone();
<[3,0][3,3][5,3]>
m.schliessen();
---System.out.println("----");
<[0,0][5,3][3,3]>
m= new Main();
<[5,3][3,3][3,0]>
m.zeigePolygone();
m.schliessen();
Komponentenbasierte SoftwareProf. Dr.
} Entwicklung
Stephan Kleuker
}
194
Ordnung beibehalten
@Entity
public class Polygon implements Serializable{
@Id @GeneratedValue
private int id;
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@OrderColumn(name="Ord")
private List<Punkt> punkte= new ArrayList<Punkt>();
@Version
private int version;
<[3,0][3,3][5,3]>
<[0,0][5,3][3,3]>
---<[3,0][3,3][5,3]>
<[0,0][5,3][3,3]>
Hinweis: persistence.xml
mit create
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
195
Standard: keine Löschfortsetzung (1/2)
public void objekteErzeugen() {
Punkt[] pkt = {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.persist(p1);
em.getTransaction().commit();
}
public void objekteBearbeiten() {
Polygon pl = em.createQuery("SELECT p FROM Polygon p",
Polygon.class).getResultList().get(0);
pl.getPunkte().remove(1);
em.getTransaction().begin();
em.persist(pl);
em.getTransaction().commit();
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
196
Standard: keine Löschfortsetzung (2/2)
public void zeigePolygoneUndPunkte() {
for (Polygon po : em.createQuery("SELECT p FROM Polygon p",
Polygon.class).getResultList())
System.out.println(po);
System.out.println("----");
for (Punkt pu : em.createQuery("SELECT p FROM Punkt p",
Punkt.class).getResultList())
System.out.println(pu);
}
public static void main(String[] args) {
Main m = new Main();
m.objekteErzeugen();
m.objekteBearbeiten();
m.zeigePolygoneUndPunkte();
m.schliessen();
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
<[0,0][3,3]>
---[0,0]
[5,3]
[3,3]
197
Löschfortsetzung
• Anmerkung: auch keine Löschung alleine durch CASCADETYPE.ALL
in Polygon, aber durch folgende Ergänzung
@OneToMany(cascade = {CascadeType.ALL},
orphanRemoval=true)
@OrderColumn(name="Ord")
private List<Punkt> punkte =
new ArrayList<Punkt>();
<[0,0][3,3]>
---[0,0]
[3,3]
• Was passiert, wenn mehrere Objekte Punkt referenzieren
(widerspricht der Eigentümerschaft)?
Exception in thread "main“ javax.persistence.RollbackException
Caused by: java.sql.SQLIntegrityConstraintViolationException:
DELETE in Tabelle 'PUNKT' hat für Schlüssel (3) die
Integritätsbedingung 'PLYGONPUNKTPNKTEID' für Fremdschlüssel
verletzt.
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
198
Vererbung – eine Tabelle (1/3)
@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
...}
199
Vererbung – eine Tabelle (2/3)
public static void main(String[] args) {
EntityManagerFactory eMF =Persistence.
createEntityManagerFactory("JPAVererbungPU");
EntityManager em=eMF.createEntityManager();
em.getTransaction().begin();
em.persist(new Buch("JPA", 2, 39.99f));
em.persist(new Produkt("Maus", 4, 7.99f));
em.persist(new Lebensmittel("Tofu", 7, 0.69f,new Date()));
em.getTransaction().commit();
for(Produkt p:(List<Produkt>)em
.createQuery("SELECT p FROM Produkt p").getResultList())
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
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
200
Vererbung – eine Tabelle (3/3)
SELECT * FROM Produkt
• Abbildung in eine Tabelle ist Default-Einstellung
• Ansatz meist am performantesten
• (float ungeeignet für Geldbeträge)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
201
Vererbung – getrennte verknüpfte Tabellen
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public class Produkt implements Serializable {
...}
SELECT * FROM Produkt
SELECT * FROM Lebensmittel
SELECT * FROM Buch
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
202
Vererbung – getrennte Tabellen
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Produkt implements Serializable {
...}
SELECT * FROM Produkt
SELECT * FROM Lebensmittel
SELECT * FROM Buch
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
203
Überwachungsmethoden (1/3)
@Entity
public class Mitarbeiter {
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private int minr;
private String name;
private void p(String s){System.out.println(s);}
@PrePersist public void prePersit() {p("prePersist");}
@PostPersist public void postPersist() {p("postPersist");}
@PreRemove public void preRemove() {p("preRemove");}
@PostRemove public void postRemove() {p("postRemove");}
@PreUpdate public void preUpdate() {p("preUpdate");}
@PostUpdate public void postUpdate() {p("postUpdate");}
@PostLoad public void postLoad() {p("postLoad");}
// Hinweis: Rollback bei einer Runtime Exception
...
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
204
Überwachungsmethoden (2/3)
public static void main(String[] args) {
EntityManagerFactory emf = Persistence
.createEntityManagerFactory("PrePostPU");
EntityManager em = emf.createEntityManager();
Mitarbeiter m = new Mitarbeiter("Olga");
prePersist
Mitarbeiter m2 = new Mitarbeiter("Otto");
prePersist
EntityTransaction ta = em.getTransaction();
prePersist
ta.begin();
postPersist
em.persist(m);
postPersist
em.persist(m2);
postPersist
em.persist(new Mitarbeiter("Urs"));
preUpdate
ta.commit();
postUpdate
ta.begin();
Mitarbeiter mm = em.find(Mitarbeiter.class, m.getMinr());
mm.setName("Anna");
em.persist(mm);
ta.commit();
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
205
Überwachungsmethoden (3/3)
ta.begin();
em.remove(m);
ta.commit();
em.close(); // notwendig für neuen Kontext
em = emf.createEntityManager();
for (Mitarbeiter m3 : em.createQuery(
"SELECT m FROM Mitarbeiter m"
, Mitarbeiter.class)
.getResultList())
System.out.println(m3.getMinr()
+ ": " + m3.getName());
em.close();
emf.close();
preRemove
postRemove
postLoad
postLoad
2: Otto
3: Urs
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
206
Interessante weitere Features in JPA
• in Anfrage-Sprache
– Funktionen auf Datentypen z. B. SUBSTRING(String,Start,Ende)
– UPDATE und DELETE in Querys
– … JOIN FETCH … garantiert Eager Loading
• immer vor/nach Persistierung ausgeführte Methoden (ersetzen
Trigger)
• Compound Primary Keys , zusammengesetzte Schlüssel über
Hilfsklassen nutzbar
• Query-Builder
@Lob @Column(name="PIC")
private byte[] picture;
@ManyToMany
@JoinTable(name="PROJEKTROLLEN„
,joinColumns=@JoinColumn(name=„ROLLEN_ID")
,inverseJoinColumns=@JoinColumn(name="PROJ_ID"))
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
207
Einbindung von Bean-Validation
• 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
• Beispiel: externe Programmierernamen beginnen mit „X“,
müssen eine der vorgegebenen Sprachen können
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
208
Validierung von Mitarbeiter
public class Mitarbeiter implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Version
private int version;
@Column(unique = true)
@Min(value=0, message="Mitarbeiternummer nicht negativ")
private int minr;
@NotNull(message="Mitarbeitername muss angegeben werden")
@Size(min=1, message="Name darf nicht leer sein")
private String name;
@OneToMany(mappedBy = "mitarbeiter", fetch = FetchType.EAGER)
@Valid
private List<Mitarbeit> mitarbeiten;
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
209
Erinnerung (1/3): eigene Validierungsannotation
• auf Mitarbeiten verteilter Aufwand darf nicht höher als für
BacklogElement angesetzter Aufwand sein
• neue Annotation BacklogElementCheck:
import javax.validation.Constraint;
import javax.validation.Payload;
import validation.validator.BacklogElementValidator;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = BacklogElementValidator.class)
@Documented
public @interface BacklogElementCheck {
String message() default "Aufwandsverteilung "
+ "bei BacklogElement beachten";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
210
Erinnerung (2/3): eigene Validierungsannotation
public class BacklogElementValidator
implements ConstraintValidator
<BacklogElementCheck,BacklogElement> {
@Override
public void initialize(BacklogElementCheck anno) {
// hier Zugriff auf Attributswerte der Annotation
}
@Override
public boolean isValid(BacklogElement value
, ConstraintValidatorContext context) {
int verteilteArbeit = 0;
for(Mitarbeit m: value.getMitarbeiten()){
verteilteArbeit += m.getGeplanteStunden();
}
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
211
Erinnerung (3/3): eigene Validierungsannotation
if (verteilteArbeit > value.getGeplanterAufwand()){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(
"Backlog-Element: Verteilter Aufwand darf nicht über "
+ "geplantem Aufwand liegen").addConstraintViolation();
return false;
}
if(value.getPhase().equals(Phase.FERTIG)
&& value.fertigstellung() < 99.99){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(
"Backlog-Element: Zum Übergang nach Fertig müssen alle "
+ "Arbeiten abgeschlossen sein").addConstraintViolation();
return false;
}
return true;
}Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
212
Nutzung der Annotation
@BacklogElementCheck
@Entity
public class BacklogElement implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Version
private int version;
@Length(min=1)
private String titel;
@Min(value=0, message = "geplanter Aufwand nicht negativ")
private int geplanterAufwand;
@OneToMany(mappedBy = "backlogElement")
@Valid
private List<Mitarbeit> mitarbeiten;
@Enumerated(EnumType.STRING)
private Phase phase;
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
213
Prüfung der Annotation lokal (1/2)- Auswertung
public class ValidateMain<T> { // T ist zu analysierende Klasse
public int analyse(T o, Class... grp) {
System.out.println("Analyse von " + o);
ValidatorFactory factory =
Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<T>> cv = validator.validate(o,grp);
for (ConstraintViolation<T> c : cv)
System.out.println(" :: " + c.getMessage());
return cv.size();
}
... // main-Methode mit Beispiel
}
• Anmerkung: Exception-Lösung leicht realisierbar
if(cv.size()>0) throw new IllegalArgumentException(...)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
214
Prüfung der Annotation lokal (2/2)
public static void main(String[] s){
ValidateMain<BacklogElement> val = new ValidateMain<>();
BacklogElement b = new BacklogElement("",-42);
Mitarbeit m1 = new Mitarbeit(30);
Mitarbeit m2 = new Mitarbeit(-3);
b.addMitarbeit(m1);
b.addMitarbeit(m2);
ein BacklogElement
val.analyse(b);
mit zwei Mitarbeiten
}
Analyse von (0)
:: Geplante Stunden nicht negativ
:: Backlog-Element: Verteilter Aufwand
darf nicht über geplantem Aufwand liegen
:: muss zwischen 1 und 2147483647 liegen
:: geplanter Aufwand nicht negativ
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
215
Prüfung der Annotation mit JPA (1/2)
public static void pruefeBacklogElement() {
Persistierer pers = new Persistierer("SprinterJSE");
BacklogElement b = new BacklogElement("HS", 42);
pers.persist(b);
Mitarbeit m1 = new Mitarbeit(30);
Mitarbeit m2 = new Mitarbeit(30);
m1.setBacklogElement(b);
m2.setBacklogElement(b);
pers.persist(m1);
pers.persist(m2);
b.addMitarbeit(m1);
b.addMitarbeit(m2);
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
216
Prüfung der Annotation mit JPA (2/2)
try {
pers.merge(b);
} catch (Exception e) { // hier RollbackException
ConstraintViolationException ctmp
= (ConstraintViolationException) e.getCause();
System.out.println(ctmp.getMessage());
for (ConstraintViolation cv : ctmp.getConstraintViolations()){
System.out.println(":: " + cv.getMessage());
}
Bean Validation constraint(s) violated
} finally {
while executing Automatic Bean Validation
pers.schliessen(); on callback event:'preUpdate'. Please
refer to embedded ConstraintViolations for
}
details.
}
:: Backlog-Element: Verteilter Aufwand
darf nicht über geplantem Aufwand liegen
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
217
Fazit
• JPA erleichtert die Zusammenarbeit von OO und DB
wesentlich
• viele Annotationen, aber oft nur eingeschränkt benötigt, da
Default-Einstellung meist passen
• @Version für optimistisches Sperren
• immer @NamedQueries nutzen
• Schlüssel einfach generieren lassen
• Wissen über SQL, Transaktionssteuerung und Nutzung von
DB selbst, bleibt wichtig
• Nachträgliche Änderungen des Objektmodells müssen meist
von Hand in Datenbanktabellen nachgezogen werden
• Vermeiden Sie (zumindest mit JEE) alle Trigger und Stored
Procedures (System wird unwartbar)
Komponentenbasierte SoftwareEntwicklung
Prof. Dr.
Stephan Kleuker
218
Herunterladen