3. Java Persistence API ( JPA ) • • • • • • • • Literatur Problem: OO in relationale DB Idee des Persistence Mapper Einfache Entity-Klasse Lebenslauf eines Entity-Objekts Umsetzung von 1:N- und M:N-Relationen Konsistenzüberwachung Vererbung Anfragen Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker • Sun Microsystems, JSR 220: Enterprise JavaBeans, Version 3.0, Java Persistence API (ejb-3_0-fr-spec-persistence.pdf, ist der Standard; trotzdem sehr gut lesbar!) http://jcp.org/aboutJava/communityprocess/final/jsr220/index.html Auch für Teile der weiteren Vorlesung • C. Bauer, G. King, Java Persistence with Hibernate, Manning, Greenwich (USA) 2007 • M. Keith, M. Schincariol, Pro EJB 3 - Java Persistence API, Apress, Berkeley (USA), 2006 74 Komponentenbasierte Software- Entwicklung 75 Prof. Dr. Stephan Kleuker Nutzung relationaler DB in OO -Programmen 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) Oberfläche + Geschäftslogik • Bisheriger Ansatz: relationale DB vorhanden, wird an OO-Programm angeschlossen. DB-Zugriffsschicht • Was ist, wenn DB zusammen mit OOProgramm entwickelt wird? • erster Ansatz: warum nicht genau so vorgehen z.B. JDBC relationale Datenbank Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 76 Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 77 Aufgabe und Ansatz Lagerverwaltung ( ein kleines OO- Modell ) Lagerraum -nummer : String beinhaltet -artikel 1 * +verkaufswertBerechnen() : float +Produkt() : Produkt +verfuegbareAnzahl() : int +verkauft(anzahl : int) : void +einlagern(anzahl : int) : void +verkaufspreis() : float - artikel bezeichnet eine Collection von Produkten in einem Lagerraum - mit verkaufspreis() wird die Mehrwertsteuer eingerechnet, die bei Büchern anders sein soll. Lebensmittel -verfallsdatum : Date +verlustAm(stichtag : Date) : float Komponentenbasierte Software- Entwicklung Produkt -name : String -lagermenge : int -preis : float Buch +verkaufspreis() : float 78 Prof. Dr. Stephan Kleuker Umgang mit Kapselung • Objekte der Klasse Produkt können leicht in folgende Tabelle übersetzt werden. Produkt OID name lagermenge preis • Objekte der im Lagerhaltungsmodell vorgestellten Klassen sollen persistent in einer relationalen DB abgespeichert werden • Bei der Anbindung an die Datenbank sollen möglichst viele OO-Errungenschaften (Kapselung, Vererbung, Polymorphismus) übernommen werden • Grundidee: Das Klassendiagramm kann ohne Methoden als ER-Diagramm gelesen werden, es beinhaltet eine 1:N und zwei 1:C Beziehungen. • Anmerkung: Dieser Übersetzungsansatz war ein beliebtes Tummelfeld von Programmierern ☺, es wird nur ein möglicher Ansatz vorgestellt. Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 79 Anmerkungen Produkt -name : String -lagermenge : int -preis : float +Produkt() : Produkt +verfuegbareAnzahl() : int +verkauft(anzahl : int) : void +einlagern(anzahl : int) : void +verkaufspreis() : float • Typischerweise werden Klassenvariablen nicht mit den jeweiligen Objekten abgespeichert, sie müssen explizit behandelt werden • Es stellt sich generell die Frage, welche zusätzlichen Informationen in welchen Tabellen verwaltet werden sollen • zusätzlicher Primary Key vergeben, falls name nicht eindeutig • Kapselungsidee wird aufgegeben, da man über die Datenbank immer direkt auf die Attribute zugreifen kann, nur durch Mehraufwand einschränkbar: – Lösung auf der Zugriffsebene im OO-Programm (umgehbar) – Spezielle Nutzer und Rechte, Nutzung von Views (Aufwand) Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 80 Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 81 Umgang mit Vererbung • Vererbung kann durch eine zusätzliche Tabelle mit einer Referenz implementiert werden, dabei ist OID Schlüssel und Fremdschlüssel aus Produkt Lebensmittel OID Umgang mit Polymorphismus • Wie funktioniert Polymorphismus? Produkt Lebensmittel -verfallsdatum : Date +verlustAm(stichtag : Date) : float • In der bisherigen relationalen Übersetzung muss sichergestellt werden, dass das richtige Objekt geladen wird • Die genaue Klassendefinition bekommt man im vorgestellten Ansatz nur, wenn man alle zu Unterklassen gehörigen Tabellen untersucht, ob die OID vorkommt • Alternativ kann man sich die genaue Klassenzugehörigkeit in einem weiteren Attribut der Tabelle Produkt merken verfallsdatum • Für jede Vererbungsebene wird eine neue Tabelle benötigt, was den Zugriff aufwändig macht • Durch die vorgeschlagene Struktur wird die Sichtweise „ein Lebensmittel ist ein Produkt“ unterstützt Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker public float verkaufswertBerechnen(){ float ergebnis=0.0f; for(Produkt tmp:artikel) ergebnis= ergebnis+ tmp.verfuegbareAnzahl()*tmp.verkaufspreis(); return ergebnis; } 82 Umgang mit Objektidentität Komponentenbasierte Software- Entwicklung 83 Prof. Dr. Stephan Kleuker Umgang mit Beziehungen zwischen Objekten • Exemplarvariablen, die Collections oder andere nicht triviale Objekttypen enthalten, müssen explizit durch eine oder mehrere Relationen oder Erweiterungen von Tabellen modelliert werden (der Ansatz ist vom Übergang vom ER-Diagramm zur Tabellenstruktur bekannt) • Objektidentität in der Datenbank wird durch den Primary Key garantiert • Wird das Objekt aus der Datenbank gelesen, ist der Nutzer verantwortlich, dass kein zweites Exemplar des gleichen Objekts geladen wird – Alternative: Beim ersten Herauslesen wird ein Flag (ein zusätzliches Attribut) gesetzt, dass sich das Exemplar außerhalb der DB befindet (Ansatz fordert viel Programmierdisziplin, insbesondere bei der Programmterminierung) beinhaltet Lagerraum -artikel 1 * Produkt Produkt OID name lagermenge preis lagerraum_OID • Frage: Warum passt die skizzierte Lösung eigentlich nicht zum UML-Diagramm? Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 84 Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 85 Bearbeiten von Objekten Zwischenfazit • Wir haben gesehen, dass einfache Objekte häufig über mehrere Tabellen verteilt werden, d.h. zum Einlesen und Bearbeiten sind relativ aufwändige SQL-JOINS notwendig • Weiterhin muss das OO-Programm die eindeutige Referenz eines Objektes in der relationalen DB kennen • Beispiel: Einlesen eines Lebensmittels, das an eine Variable mit Namen „banane“ gebunden war • Selbst bei Beispielen, in denen der Einsatz relationaler Datenbanken sinnvoller Standard ist (Lagerverwaltung), wird die Verknüpfung eines OO-Programms mit relationaler Datenhaltung aufwändig • Die Nutzung relationaler Datenbanken in OO-Programmen sollte immer über eine DB-Kapselung, die den Zugriff auf die DB regelt, erfolgen • Für den Kapselungsansatz stehen verschiedene Produkte zur Verfügung (z. B. Hibernate, TopLink) , JPA spezifiziert gemeinsames Interface SELECT name, lagermenge, preis, verfallsdatum FROM Identifikation, Lebensmittel, Produkt WHERE Identifikation.Name = 'banane' AND Identifikation.OID = Produkt.OID AND Produkt.OID = Lebensmittel.OID Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker • Verwandte Ansätze: direkte Nutzung einer OO-Datenbank, Programmierung mit JDO 86 Konzept von Persistence - Mappern Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 87 Beispiel ( 1 / 6 ) package jpabeispiel1; import javax.persistence.Entity; import javax.persistence.Id; @Entity public class Mitarbeiter { @Id private int minr; private String name; • Persistence-Mapper (PM) muss wissen, welche Objekte persistiert werden sollen -> markiere Klassen, deren Objekte verwaltet werden sollen • Persistence-Mapper muss Beziehungen zwischen Objekten kennen • Ablauf: Nutzer benutzt Objekt unter Verwaltung; dann übernimmt PM die Überwachung, führt Änderungen aus, regelt den Zugriff mehrerer Nutzer • Ablauf: Nutzer erzeugt Objekt und muss PM einmal mitteilen, dieses zu verwalten • PM kann selbst regeln, wie Aufgaben realisiert werden (Tabellenstruktur, Transaktionssteuerung) public Mitarbeiter(){} //leerer 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 Software- Entwicklung Prof. Dr. Stephan Kleuker 88 Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 89 Beispiel ( 2 / 6 ) Beispiel ( 3 / 6 ) • Persistence.xml liegt im Ordner META-INF (projektartabhängig) • Detaileinstellungen von JPA-Realisierung abhängig (z. B. Hibernate, TopLink) <?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="JPABeispiel1PU" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <class>jpabeispiel1.Mitarbeiter</class> Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 90 Beispiel ( 4 / 6 ) <properties> <property name="hibernate.connection.username" value="kleuker"/> <property name="hibernate.connection.driver_class" value="org.apache.derby.jdbc.ClientDriver"/> <property name="hibernate.connection.password" value="kleuker"/> <property name="hibernate.connection.url" value="jdbc:derby://localhost:1527/ProjVer"/> <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/> <property name="hibernate.hbm2ddl.auto" value="update"/> </properties> </persistence-unit> </persistence> Komponentenbasierte Software- Entwicklung 91 Prof. Dr. Stephan Kleuker Beispiel ( 5 / 6 ) package jpabeispiel1; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public void datenZeigen() { for (Mitarbeiter m : (List<Mitarbeiter>) em.createQuery("SELECT m FROM Mitarbeiter m"). getResultList()) { System.out.println(m.getMinr() + ": " + m.getName()); } } public class Main { private EntityManagerFactory emf = Persistence. createEntityManagerFactory("JPABeispiel1PU"); private EntityManager em = emf.createEntityManager(); public static void main(String[] args) { Main m = new Main(); m.beispieldaten(); m.datenZeigen(); m.beenden(); // mit em.close() } 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(); } 0: 1: 2: 3: Egon Erwin Ute Aische } Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 92 Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 93 Beispiel ( 6 / 6 ) 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); tr.commit(); } } • Falls noch keine Tabelle Mitarbeiter existiert, wird diese angelegt } Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 94 Entity ist POJO Prof. Dr. Stephan Kleuker 95 Primary Key Entity-Klassen-Objekte sind klassische Plain Old Java Objects • Verpflichtung: – public (oder protected) Default-Konstruktor – Exemplarvariablen private oder protected, Zugriff über get... und set... – Klasse, Methoden, Exemplarvariablen nicht final – Serialisierbar [zumindest sehr sinnvoll] • keine weiteren Einschränkungen – beliebige weitere Methoden – Vererbung (auch Entity von Nicht-Entity [aufwändig]) – Nutzung abstrakte Klassen Komponentenbasierte Software- Entwicklung Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 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 96 Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 97 Persistierbare Typen / Klassen Persistent Fields / Properties • Primitive Typen (byte, char, int, long, float, double, boolean) • java.lang.String • Andere serialisierbare Typen: – Wrapper der primitiven Typen – java.math.BigInteger – java.math.BigDecimal – java.util.Date, java.util.Calendar – java.sql.Date, java.sql.Time, java.sql.TimeStamp – Nutzerdefinierte serialisierbare Typen – byte[], Byte[], char[], Character[] • Enumeration • Andere Entities • Collections von Entities (Collection, Set, List, Map) Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 98 Annotationen zur Flexibilisierung Prof. Dr. Stephan Kleuker Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 99 PersistenceContext @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 Software- Entwicklung • 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 • • • • • 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.flush(); // in Transaktion 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) 100 Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 101 Beispiel fü r Cache ( 1 / 2 ) Beispiel fü r Cache ( 2 / 2 ) @Entity public class Mitarbeiter implements Serializable { 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) org.hibernate.util.JDBCExceptionReporter logExceptions WARNUNG: SQL Error: -1, SQLState: 22001 SCHWERWIEGEND: Bei dem Versuch, VARCHAR 'JFK' auf die Länge 2 zu kürzen, ist ein Abschneidefehler aufgetreten. @Id @GeneratedValue private int minr; @Column(length=2) //maximal zwei Zeichen private String name; public Mitarbeiter() { } //leerer Konstruktor benötigt public Mitarbeiter(String name) { this.name = name; } // get- und set-Methoden weggelassen public String toString(){ return name+"("+minr+")"; } }Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 102 Lebenslauf eines Entity - Objekts Prof. Dr. Stephan Kleuker Prof. Dr. Stephan Kleuker 103 Seltsames merge NEW -> merge() führt evtl. zur Mehrfachobjekterzeugung refresh() nur, wenn vorher persistiert Komponentenbasierte Software- Entwicklung Komponentenbasierte Software- Entwicklung 104 public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAMergePU"); EntityManager em = emf.createEntityManager(); Mitarbeiter m1 = new Mitarbeiter("Ford"); Mitarbeiter m2 = new Mitarbeiter("Arthur"); em.getTransaction().begin(); em.merge(m1); em.merge(m1); 1: Ford em.merge(m1); 2: Ford em.persist(m1); 3: Ford em.merge(m1); em.persist(m2); 4: Ford em.merge(m2); 5: Arthur em.getTransaction().commit(); for (Mitarbeiter m : (List<Mitarbeiter>) em. createQuery("SELECT m FROM Mitarbeiter m"). getResultList()) System.out.println(m.getMinr() + ": " + m.getName()); } Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 105 Wichtiges / korrektes merge 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 public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPAMergePU"); EntityManager em = emf.createEntityManager(); Mitarbeiter m1 = new Mitarbeiter("Ford"); em.getTransaction().begin(); em.persist(m1); em.getTransaction().commit(); Mitarbeiter m3= em.find(Mitarbeiter.class,1); m1.setName("Trilian"); em.merge(m1); System.out.println(m3.getMinr() + ": " + m3.getName()); // trotzdem besser: Änderungen in Transaktionen em.close(); 1: Trilian } Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 106 Generelle JEE - Regel Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 107 Einschub : XML- Konfiguration Convention over Configuration • bedeutet: wenn nichts angegeben wird, wird ein DefaultWert genutzt • Default-Werte sind zwar sinnvoll, sollte man aber kennen • Statt Annotationen zu nutzen, können diese Informationen auch in XML beschrieben werden • Typisch: einen XML-Datei pro Klasse + zusammenführende XML-Datei • Vorteil: Verhaltensänderungen ohne Codeänderung • Nachteil: viele kleine penibel zu pflegende Dateien • Erinnerung: Java-Inkonsistenz • Auch möglich: XML und Annotationen; dabei „schlägt“ XML die Annotationen Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 108 Komponentenbasierte Software- Entwicklung Prof. Dr. Stephan Kleuker 109