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