5. 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 218 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 219 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 220 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 221 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 222 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 223 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.1" 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_1.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 224 Beispiel (3/7) <properties> <property name="javax.persistence.jdbc.url" value="jdbc:derby://localhost:1527/Spielerei2"/> <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="create-tables"/> <property name = "javax.persistence.schema-generation.database.action" value="create"/> </properties> </persistence-unit> </persistence> Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 225 Beispiel (4/7) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 226 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 = 1; i <= namen.length; i++) em.persist(new Mitarbeiter(i, namen[i-1])); em.getTransaction().commit(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 227 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 228 Beispiel (7/7) • Falls keine Tabelle Mitarbeiter existiert, wird diese angelegt Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 229 Projektaufbau selber einfügen http://www.eclipse.org/downloads/download.php?file=/rt/eclipselink/release s/2.6.4/eclipselink-2.6.4.v20160829-44060b6.zip z.B. von Eclipse-WebSeite Alternativ: vorhandene Bibliothek Praxis: Build-Programm zum einheitlichen Laden von Bibliotheken (Maven, Gradle) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 230 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 SoftwareProf. Dr. 231 } Entwicklung Stephan Kleuker 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 232 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 (bei JavaSE theoretisch verzichtbar) • keine weiteren Einschränkungen – beliebige weitere Methoden – Vererbung (auch Entity von Nicht-Entity [aufwändig]) – Nutzung abstrakte Klassen Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 233 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 Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 234 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 235 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 236 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 237 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 238 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().begin(); 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 Transaktionen , ggfls. Auch EntityManager (erzeugen, Aktion, schließen) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 239 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 240 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]: 2016-10-29 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 241 Lebenslauf eines Entity-Objekts NEW -> merge() führt evtl. zur Mehrfachobjekterzeugung refresh() nur sinnvoll, wenn vorher persistiert Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 242 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 243 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 244 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 245 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 246 Lebenslaufanalyse (5/13) - Beispielschritte m.setName("Deep Thought"); beinhaltet(3, m); PC 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 247 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 248 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 249 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 m = em.merge(m); beinhaltet(14, m); beinhaltet(15, m2); 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 250 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 – will man sicher das aktuelle Objekt aus der DB haben, muss refresh(.) genutzt werden – 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 251 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 252 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 1 Nono Komponentenbasierte SoftwareEntwicklung id Prof. Dr. Stephan Kleuker name 253 Lebenslaufanalyse (12/13) – Zweites Beispiel em.persist(m); em.getTransaction().begin(); em.getTransaction().commit(); beinhaltet(4, m); m2 em.persist(m2); beinhaltet(5, m2); 1 Nono em.refresh(m2); beinhaltet(6, m2); m PC 1 ich 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 254 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 255 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) { this.em.getTransaction().begin(); try { this.em.persist(object); this.em.getTransaction().commit(); } catch (Exception e) { if (this.em.getTransaction().isActive()) this.em.getTransaction().rollback(); throw e; // oder neue Exception } finally { // evtl. this.em.close(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 256 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) { this.emf = Persistence .createEntityManagerFactory(persistence); this.em = emf.createEntityManager(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 257 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 258 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 } 259 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 260 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 261 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 262 Umgang mit komplexen Typen • typischerweise stehen Collections für 1:N oder M:NBeziehungen, die bei der Persistierung zu beachten sind; dies wird auf nachfolgenden Folien diskutiert • sind es nur auf das eine Objekt bezogene Informationen, müssen sie nicht weiter behandelt werden private List<String> kommentare; wird zu einem serialisieren Eintrag (Blob) • ähnlich verhält es sich anderen Objekten, die nur zu einem Objekt gehören und dann nicht extra als @Entity zu kennzeichnen sind private Adresse adresse; Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 263 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 264 Umsetzung unidirektional 1:N (1/4) @Entity public class Projektauftrag implements Serializable { @Id @GeneratedValue private int paid; private String titel; public String getTitel() { return titel; } public void setTitel(String titel) { this.titel = titel; } } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 265 Umsetzung unidirektional 1:N (2/4) @Entity public class Projekt implements Serializable { @Id @GeneratedValue private int projektid; private String name; @OneToMany(cascade=CascadeType.PERSIST) private Set<Projektauftrag> auftraege; public Projekt(){ auftraege = new HashSet<Projektauftrag>(); } // fehlen get- und set-Methoden (auch add wäre sinnvoll) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 266 Umsetzung unidirektional 1:N (3/4) public void datenAnlegen(){ String p[] ={"Hotel", "Noten", "Belegung"}; String a[] ={"Analyse", "Modell", "Design"}; em.getTransaction().begin(); for (int i=0; i<p.length;i++){ Projekt pr= new Projekt(); pr.setName(p[i]); for (int j=0; j<a.length;j++){ Projektauftrag pa= new Projektauftrag(); pa.setTitel(a[j]); Set<Projektauftrag> tmp= pr.getAuftraege(); tmp.add(pa); } em.persist(pr); } em.getTransaction().commit(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 267 Umsetzung unidirektional 1:N (4/4) 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; Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 268 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 269 Umsetzung bidirektional 1:N (1/5) - 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 270 Umsetzung bidirektional 1:N (2/5) @Entity public class Projektauftrag implements Serializable { @Id @GeneratedValue private int paid; private String titel; @ManyToOne(cascade={ CascadeType.PERSIST ,CascadeType.MERGE}) private Mitarbeiter bearbeiter; //... Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 271 Umsetzung bidirektional 1:N (3/5) @Entity public class Mitarbeiter implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) private int minr; private String name; @OneToMany(mappedBy="bearbeiter", cascade={CascadeType.PERSIST, CascadeType.MERGE}) private Set<Projektauftrag> auftraege = new HashSet<Projektauftrag>(); public void auftragHinzu(Projektauftrag pa){ auftraege.add(pa); } // ... Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 272 Umsetzung bidirektional 1:N (4/5) public void mitarbeiterZuordnen(){ Mitarbeiter m[]= {new Mitarbeiter("Egon"), new Mitarbeiter("Aise"), new Mitarbeiter("Urs")}; em.getTransaction().begin(); for (int i=0; i<m.length; i++) em.persist(m[i]); for (Projektauftrag p : (List<Projektauftrag>) em .createQuery("SELECT p FROM Projektauftrag p") .getResultList()) if(p.getTitel().equals("Analyse")){ JPA 1.0m[0].auftragHinzu(p); Variante der p.setBearbeiter(m[0]); Anfrage } else{ m[1].auftragHinzu(p); p.setBearbeiter(m[1]); } em.getTransaction().commit(); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 273 Umsetzung bidirektional 1:N (5/5) SELECT MITARBEITER.NAME, MITARBEITER.MINR, PROJEKTAUFTRAG.TITEL, PROJEKTAUFTRAG.PAID FROM MITARBEITER LEFT JOIN PROJEKTAUFTRAG ON (MITARBEITER.MINR=PROJEKTAUFTRAG.BEARBEITER_MINR) ; Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 274 Umsetzung ist OR-Mapper freigestellt Man sieht, dass bei bidirektionalen Beziehungen in EclipseLink keine neuen Tabellen angelegt werden, bei unidirektionalen standmäßig schon (verhinderbar mit @JoinColumn(name=„Vari ablenname_Gegenseite“)) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 275 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<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 276 Umsetzung bidirektional M:N (1/4) Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 277 Umsetzung bidirektional M:N (2/4) @Entity public class Projektauftrag implements Serializable { @Id @GeneratedValue private int paid; private String titel; @ManyToOne(cascade={ CascadeType.PERSIST ,CascadeType.MERGE}) private Mitarbeiter bearbeiter; @ManyToOne(cascade={ CascadeType.PERSIST ,CascadeType.MERGE}) private Rolle rolle; @Version private int version; Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 278 Umsetzung bidirektional M:N (3/4) @Entity public class Rolle implements Serializable { @ID @GeneratedValue private int rid; private String name; private int tagessatz; @ManyToMany(cascade = { CascadeType.PERSIST ,CascadeType.MERGE}) @Basic(fetch = FetchType.LAZY) private List<Mitarbeiter> mitarbeiter; @Version private int version; public void mitarbeiterHinzu(Mitarbeiter m) { mitarbeiter.add(m); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 279 Umsetzung bidirektional M:N (4/4) @Entity public class Mitarbeiter implements Serializable { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int minr; private String name; @OneToMany(mappedBy = "bearbeiter" ,cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @Basic(fetch = FetchType.LAZY) private Set<Projektauftrag> auftraege; @ManyToMany(mappedBy = "mitarbeiter" ,cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @Basic(fetch = FetchType.LAZY) private Set<Rolle> rollen = new HashSet<Rolle>(); @Version private int version; public void rolleHinzu(Rolle r) { rollen.add(r); } Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 280 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 281 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 282 Anfrageausführung • 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 283 Einfache Beispiele Vorbemerkung: In FROM-Zeilen stehen Klassen und Attributnamen; bei diesen muss Groß- und Kleinschreibung beachtet werden! SELECT p FROM Projekt p • Projekt Bonitaet (5) [Konten Historie Raten ] • Projekt Bremse (11) [Display Sensoren Fusion ] Direkter Zugriff auf Attribute SELECT p.name FROM Projekt p • Bonitaet • Bremse Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 284 Nutzung mehrwertiger Relationen (1/2) SELECT p.auftraege FROM Projekt p • Aufgabe Raten (10) durch Ivan als Java • Aufgabe Konten (6) durch Fatma als Cobol • Aufgabe Historie (8) durch Urs als Java • Aufgabe Sensoren (12) durch Ivan als C • Aufgabe Display (14) durch Fatma als Java • Aufgabe Fusion (15) durch Ivan als C Nicht erlaubt: SELECT p.auftraege.bearbeiter FROM Projekt p • An exception occurred while creating a query in EntityManager: Exception Description: Error compiling the query [SELECT p.auftraege.bearbeiter FROM Projekt p], line 1, column 9: invalid navigation expression [p.auftraege.bearbeiter], cannot navigate collection valued association field [auftraege]. Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 285 Nutzung mehrwertiger Relationen (2/2) SELECT pa.bearbeiter FROM Projektauftrag pa 1: Ivan Auftraege=[ Sensoren Fusion Raten ] Rollen=[ C Java ] 1: Ivan Auftraege=[ Sensoren Fusion Raten ] Rollen=[ C Java ] 1: Ivan Auftraege=[ Sensoren Fusion Raten ] Rollen=[ C Java ] 2: Fatma Auftraege=[ Konten Display ] Rollen=[ Cobol Java ] 2: Fatma Auftraege=[ Konten Display ] Rollen=[ Cobol Java ] 3: Urs Auftraege=[ Historie ] Rollen=[ Java ] Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 286 Neu zusammengesetzte Ergebnisse • Folgende Folie zeigt woher Details der Ausgabe kommen SELECT r.name, r.tagessatz FROM Rolle r • [Ljava.lang.Object;@1db5ec :: [Ljava.lang.Object; C 50 • [Ljava.lang.Object;@92b1a1 :: [Ljava.lang.Object; Java 60 • [Ljava.lang.Object;@cbf9bd :: [Ljava.lang.Object; Cobol 70 Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 287 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 288 JOIN-Varianten SELECT m.name FROM Projektauftrag pa, Mitarbeiter m WHERE pa.bearbeiter=m AND pa.titel='Sensoren' SELECT m.name // deutlich objektorientierter FROM Projektauftrag pa JOIN pa.bearbeiter m WHERE pa.titel='Sensoren' • Ivan SELECT DISTINCT(pa.bearbeiter.name) FROM Projekt p JOIN p.auftraege pa WHERE p.name='Bremse' • Fatma • Ivan Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 289 Vordefinierte Anfragen + typisches Problem private static final String PROJEKT_VON= "SELECT DISTINCT(p.name) " +"FROM Projekt p JOIN p.auftraege pa " +"WHERE pa.bearbeiter.name= :mname"; public void projektVon(String name){ Query query = em.createQuery(PROJEKT_VON) .setParameter("mname", name); Collection erg = query.getResultList(); for (Iterator it = erg.iterator(); it.hasNext();) { System.out.println(it.next()); } } projektVon("Urs' OR NOT(p.name='bla') OR p.name='bla") • Bremse • Bonitaet Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 290 echte Standardkonformität? • • • • 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‘ [1: Ivan... • ging lange Zeit nicht in EclipseLink: SELECT m FROM Mitarbeiter m JOIN m.rollen r WHERE r.name='C' invalid navigation expression [r.name], cannot navigate expression [r] of type [java.util.List] inside a query. • trotzdem: Immer sinnvoll Objektauswahlen in Anfragesprache durchzuführen, nicht alle Objekte aus DB lutschen und dann verarbeiten Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 291 Klassische SQL-Operatoren SELECT p.name, COUNT(pa.titel) FROM Projekt p JOIN p.auftraege pa GROUP BY p.name • [Ljava.lang.Object;@4a9a7d :: [Ljava.lang.Object; Bonitaet 3 • [Ljava.lang.Object;@1e4a47e :: [Ljava.lang.Object; Bremse 3 • Erinnerung AVG, SUM, MIN, MAX Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 292 Named Queries @NamedQueries({ //in Mitarbeiter (@NamedQuery(name="Mitarbeiter.primaryKey" ,query="SELECT m FROM Mitarbeiter m WHERE m.minr= :minr"), @NamedQuery(name="Mitarbeiter.mitFaehigkeit" ,query="SELECT m FROM Rolle r JOIN r.mitarbeiter m" +" WHERE r.name = :name") }) // z. B. in Main mit lokalen Variablen minr und faehigkeit Mitarbeiter m = em .createNamedQuery("Mitarbeiter.primaryKey") .setParameter("minr", minr) .getSingleResult(); for( Mitarbeiter m:em // EclipseLink Object m:em .createNamedQuery( "Mitarbeiter.mitFaehigkeit") .setParameter("name", faehigkeit) .getResultList()) System.out.println(m); Komponentenbasierte SoftwareEntwicklung Prof. Dr. Stephan Kleuker 293 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 294 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 295 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 296 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 } 297 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 298 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 299 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] 300 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 301 Vererbung – eine Tabelle (1/3) @Entity @Inheritance(strategy=InheritanceType.SINGLE_TABLE) 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 ...} 302 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 303 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 304 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 305 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 306 Ü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 307 Ü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 308 Ü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 309 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 310 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 311 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 312