Literatur [14-1] Müller, Bernd; Wehr, Harald: Java Persistence API 2. Hanser, 2012. [14-2] http://www.jpainfo.de/projekte/download.html [14-3] Beeger, Robert et al.: Hibernate. Persistenz in Java-Systemen mit Hibernate 3. dpunkt, 2006 [14-4] http://sourceforge.net/projects/hibernatesample/ [14-5] http://hibernate.org/ [14-6] http://mvnrepository.com/artifact/org.hibernate [14-8] http://upload.wikimedia.org/wikipedia/commons/8/81/Java_Persistence.p df Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 2 Was macht Hibernate? • Hibernate realisiert die JPA-Schnittstelle in der Version 2.0. Siehe: http://de.wikipedia.org/wiki/Hibernate_(Framework) • JPA = Java Persistence API Siehe: http://de.wikipedia.org/wiki/Java_Persistence_API Die Fehlermeldungen sind meist besser als beim nativen Hibernate, trotzdem noch grausig. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 3 Maven und Hibernate <hibernate.version>4.3.7.Final</hibernate.version> … … … <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> <version>1.0.2</version> Es soll nur die Java-API </dependency> benutzt werden. <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>${hibernate.version}</version> <exclusions> <exclusion> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.0-api</artifactId> </exclusion> </exclusions> </dependency> Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 4 Klasse Person I @Entity public class Person implements Serializable { @Id @GeneratedValue(strategy= GenerationType.AUTO) private Long id; private String firstName; private String lastName; public Person() {} public Person(String firstName, String lastName) { this.firstName= firstName; this.lastName= lastName; } … … Getter und Setter … … @Override public String toString() { return String.format("####---Id=%d: %s %s",id,firstName, lastName); } Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 5 persistence.xml I <?xml version="1.0" encoding="UTF-8"?> <persistence 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_2_0.xsd" version="2.0"> <persistence-unit name="bankexample" transaction-type="RESOURCE_LOCAL"> … … Optionen … … </persistence-unit> </persistence> Name des Persistenzkontextes Java-SEUmgebung Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 7 persistence.xml II <description>Ein einfaches Beispiel fuer 1 oder 2 Klassen</description> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <class>de.htw_berlin.f4.kbe.jpa.Person</class> <properties> <!-- JDBC properties --> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/bankexample" /> <property name="javax.persistence.jdbc.user" value="root" /> <property name="javax.persistence.jdbc.password" value="" /> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> <!-- Hibernate properties --> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/> <property name="hibernate.hbm2ddl.auto" value="create"/> </properties> Siehe u.a. http://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/configuration.html Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 8 persistence.xml III - Erläuterungen • Die Datei persistence.xml ist die zentrale Konfigurationsdatei. • Mit Properties werden die Parameter den einzelnen Komponenten zugeordnet. • Wie beim nativen Hibernate gibt es SQL-Dialekte: http://javamanikandan.blogspot.in/2014/05/sql-dialects-inhibernate.html • Die Hibernate-Property hibernate.hbm2ddl.auto steuert das automatische Erzeugen und Vernichten der Tabellen in der Datenbank, folgende Werte sind möglich: – validate: nur Prüfen des vorhandenen Schemas – update: Aktualisieren des vorhandenen Schemas – create: Vollständiges Erzeugen des Schemas zum Beginn – create-drop: create und nach dem Lauf Löschen des Schemas Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 9 Ort für persistence.xml I | pom.xml +---src | +---main | | +---java Mit Hilfe von maven wird | | | \---de die Datei an die richtige | | | \---htw_berlin Stelle kopiert. | | | \---f4 | | | \---kbe | | | \---jpa (Sie muss im jar-File im META-INF-Bereich sein) | | | App.java | | | Person.java | | | \---target | | \---resources +---classes | | \---META-INF | | | | persistence.xml | +---de | | \---htw_berlin | | \---f4 | | \---kbe So muss bei netbeans der | | \---jpa SRC-Baum aussehen. | | App.class | | Person.class | | | \---META-INF | persistence.xml 10 Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 Ort für persistence.xml II - netbeans Die Datei und der Ordner META-INF können über Filesystem erzeugt werden oder nach CTRL_n: Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 11 Das Hauptprogramm I - Schema private void runner() { ...createFactory("bankexample"); EntityManager em= emf.createEntityManager(); em.getTransaction().begin(); … … … Alle Aktivitäten … … … em.getTransaction().commit(); em.close(); emf.close(); } Wenn keine Daten verändert werden, z.B. mit Selects, dann kann die Klammerung in einer Transaktion fortfallen. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 12 Das Hauptprogramm II private void runner() { createFactory("bankexample"); EntityManager em= emf.createEntityManager(); Person person; em.getTransaction().begin(); person= new Person("Heidi","Musterfrau"); System.out.println(person); em.persist(person); System.out.println(person); System.out.println("Heidi ist gespeichert? --> "+em.contains(person)); person= new Person("Paul","MusterMann"); em.persist(person); System.out.println(person); person= new Person("Erika","Rödenhufer"); em.persist(person); System.out.println(person); em.getTransaction().commit(); em.close(); emf.close(); } Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 13 Das Hauptprogramm III - createFactory() public class App { EntityManagerFactory emf; private void setEntityManagerFactory(String persistenceUnitName, Map properties) { … … emf= provider.createEntityManagerFactory(persistenceUnitName, properties); } private void createFactory(String persistenceUnit) { setEntityManagerFactory(persistenceUnit,null); } public static void main( String[] args ) { (new App()).runner(); } Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 14 Output des ersten Laufs (01) (02) (03) (04) (05) (06) (07) (08) (09) (10) (11) (12) Hibernate: drop table if exists Person Hibernate: create table Person (id bigint not null auto_increment, firstName varchar(255), lastName varchar(255), primary key (id)) ENGINE=InnoDB ####---Id=null: Heidi Musterfrau Hibernate: insert into Person (firstName, lastName) values (?, ?) ####---Id=1: Heidi Musterfrau Heidi ist gespeichert? --> true Hibernate: insert into Person (firstName, lastName) values (?, ?) ####---Id=2: Paul MusterMann Hibernate: insert into Person (firstName, lastName) values (?, ?) ####---Id=3: Erika Rödenhufer In Zeile (05) ist die ID noch nicht bestimmt, erst nach dem persist()-Aufruf. Die Zeilen mit "Hibernate:" sind die an die Datenbank gesendeten SQL-Statements. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 15 Datenbank "bankexample" Nun schauen wir uns das generierte Schema mit den gespeicherten Werten an. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 16 Warnung wegbekommen I WARN: HHH015016: Encountered a deprecated javax.persistence.spi.PersistenceProvider [org.hibernate.ejb.HibernatePersistence]; use [org.hibernate.jpa.HibernatePersistenceProvider] private void setEntityManagerFactory(String persistenceUnitName, Map properties) { emf= null; PersistenceProvider fallBackProvider= null; List<PersistenceProvider> providers = PersistenceProviderResolverHolder .getPersistenceProviderResolver().getPersistenceProviders(); for(PersistenceProvider provider : providers) { if(provider instanceof org.hibernate.ejb.HibernatePersistence) { fallBackProvider= provider; System.out.println("#### "+provider+" ++++ skip"); continue; } System.out.println("#### "+provider); emf= provider.createEntityManagerFactory(persistenceUnitName, properties); if(emf!=null) { break; } } Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 17 Warnung wegbekommen II if(emf==null) { if(fallBackProvider==null) { throw new RuntimeException(„..."); } emf= fallBackProvider.createEntityManagerFactory (persistenceUnitName,properties); } } Output: #### org.hibernate.ejb.HibernatePersistence@6d311334 ++++ skip #### org.hibernate.jpa.HibernatePersistenceProvider@682a0b20 Es gibt also zwei Provider, wobei der nicht zu benutzende zuerst angeboten wird – wir nehmen den zweiten und die Warnung ist weg. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 18 Optionen angeben private void createFactoryWithOptions(String persistenceUnit) { Map<String, Object> configOverrides= new HashMap<>(); configOverrides.put("hibernate.hbm2ddl.auto","create-drop"); setEntityManagerFactory(persistenceUnit,configOverrides); } Beim Erzeugen der Factory können noch Werte für Properties in Form einer HashMap angegeben werden, hier wird der Wert für hibernate.hbm2ddl.auto auf create-drop gesetzt. Statt createFactory() wird dann createFactoryWithOptions() benutzt. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 19 Alternativer ID-Generator I @Id @GeneratedValue(strategy= GenerationType.TABLE) private Long id; Es werden nun zwei Tabellen zum Verwalten der vergebenen ID-Werte angelegt und von Hibernate benutzt – dies geht bei allen Datenbanken. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 20 Alternativer ID-Generator II @Id @GeneratedValue(strategy= GenerationType.TABLE, generator= "personGenerator") @TableGenerator( name= "personGenerator", table= "ID_GEN", pkColumnName= "GEN_KEY", valueColumnName= "GEN_VALUE", pkColumnValue= "PERSON_ID", AllocationSize= 10 ) private Long id; Die Tabelle für die ID-Verwaltung kann explizit benannt und samt allen Spalten definiert werden. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 21 Selbst verwaltete IDs I @Entity public class Person implements Serializable { private String id; private String firstName; private String lastName; public Person() { this.id= UUID.randomUUID().toString(); } public Person(String firstName, String lastName) { this(); this.firstName= firstName; this.lastName= lastName; } Zugriff auf ID nur @Id über Getter/Setter public String getId() { return id; } public void setId(String id) { Es muss nun auch this.id= id; ein Setter für ID } vorhanden sein. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 22 Selbst verwaltete IDs II So sehen dann die generierten IDs aus. 23 Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 Lesen eines Objekts em.getTransaction().begin(); person= new Person("Heidi", "Musterfrau"); em.persist(person); String pKey= person.getId(); System.out.println(person); person= new Person("Paul", "MusterMann"); em.persist(person); System.out.println(person); person= new Person("Erika", "Rödenhufer"); em.persist(person); System.out.println(person); person= em.find(Person.class, pKey); System.out.println(person); em.getTransaction().commit(); ####---Id=d121cadd-c220-49a7-bc1b-6230a489452a: ####---Id=9678a802-f760-44c4-847d-94f0088ec5ae: ####---Id=68482017-8b0f-481b-bbb9-5a0405345fd7: ####---Id=d121cadd-c220-49a7-bc1b-6230a489452a: Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 Heidi Musterfrau Paul MusterMann Erika Rödenhufer Heidi Musterfrau 24 Variation der Datentypen I @Entity public class Person implements Serializable { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; @Column(nullable=true, length= 20) private String firstName; @Column(name="name", length= 20) private String lastName; @Temporal(TemporalType.DATE) private Date birthday; @Temporal(TemporalType.TIME) private Date birthtime; @Transient private Date adminDate; @Lob char[] mark; @Enumerated(EnumType.ORDINAL) private Sex sex; @Enumerated(EnumType.STRING) private Salutation salutation; @Column(precision= 10, scale= 2) private BigDecimal purse; Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 25 Variation der Datentypen II public Person(String firstName, String lastName, String birthday) { DateFormat format= new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); this.firstName= firstName; this.lastName= lastName; try { this.birthday= format.parse(birthday); this.birthtime= format.parse(birthday); } catch (ParseException e) { throw new IllegalArgumentException("Wrong Date format"); } } Der Konstruktor muss nun etwas anders aussehen. Hier wie mit Zeitformaten umgegangen werden kann. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 26 Enums und das Hauptprogramm public enum Sex { WOMAN, MAN } public enum Salutation { Ms, Mr, Dr, Prof } char[] abc= {'a','b','c'}; createFactory("bankexample"); EntityManager em= emf.createEntityManager(); Person person; … … … person= new Person("Heidi", "Musterfrau","02.10.1970 18:13:10"); em.persist(person); person.setSex(Sex.WOMAN); person.setSalutation(Salutation.Dr); person.setMark(abc); person.setPurse(new BigDecimal("12.5")); Long pKey= person.getId(); System.out.println(person); person= em.find(Person.class, pKey); System.out.println(person); Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 27 Datenbank Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 28 1:1-Relation unidirektional I Person Address 1 Inverse Seite 1 Besitzende Seite Ebene der Objekte Ebene der Tabellen Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 29 1:1-Relation unidirektional II @Entity public class Person implements Serializable { @Id @GeneratedValue(strategy= GenerationType.AUTO) private Long id; private String firstName; private String lastName; //@OneToOne @OneToOne(cascade= CascadeType.ALL, orphanRemoval= true) @JoinColumn(name= "myAddress", nullable= true) private Address address; JoinColumn ist die Spalte … Getter/Setter … mit dem Fremdschlüssel und charakterisiert die besitzende @Entity public class Address implements Serializable{ Seite @Id @GeneratedValue(strategy= GenerationType.AUTO) private Long id; private String city; private String street; private int houseNumber; … Getter/Setter … Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 30 1:1-Relation unidirektional III Ohne Kaskadieren em.getTransaction().begin(); muss das Address-Objekt address= new Address("Berlin","Gustavzeile",3); explizit gespeichert werden //em.persist(address); person= new Person("Heidi", "Musterfrau",address); em.persist(person); System.out.println(person); address= new Address("Fürstenwalde","Karl-May-Allee",23); person= new Person("Gudrun", "von Oiouten",address); em.persist(person); System.out.println(person); person.setAddress(null); //em.detach(person); Ohne nullable= true em.remove(person); ergibt dies eine wenig em.getTransaction().commit() aussagende Fehlermeldung Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 31 1:1-Relation unidirektional IV - Bemerkungen • Wenn bei der besitzenden Klasse (dort, wo das Join-Attribut sich befindet) nur ein @OneToOne steht, dann müssen alle beteiligten Objekte mit em.persist() gespeichert werden. • Wenn das Kaskadierden mit @OneToOne(cascade=CascadeType.ALL) eingeschaltet ist, dann werden alle mit Referenzen verbundenen Entities in der Datenbank gespeichert. • Mit dem Parameter orphanRemoval= true werden nicht per Referenz verbundene Entities auch in der Datenbank gelöscht. Das Setzen der Adresse mit null und anschließenden Löschen mit em.remove(person) führt ansonsten zu einem Waisen. Mit em.remove() wird das Objekt in der Datenbank gelöscht, mit em.detach() wird es nur dem Programm entzogen, verbleibt aber in der Datenbank. Die beiden Operationen em.remove() und em.detach() betreffen nur das JPA, nicht die Objekte im RAM des Programms. Diese werden ganz normal mit dem Garbage-Collector entfernt. • • Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 32 1:1-Relation unidirektional V - Schemata Besitzer-Seite JoinColumn mit Originalnamen JoinColumn mit geänderten Namen Inverse Seite 33 Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 1:1-Relation bidirektional I Person Address 1 Inverse Seite Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 1 Besitzende Seite Ebene der Objekte Ebene der Tabellen 34 1:1-Relation bidirektional II @Entity public class Person implements Serializable { @Id @GeneratedValue(strategy= GenerationType.AUTO) private Long id; private String firstName; private String lastName; @OneToOne(cascade= CascadeType.ALL) Besitzende Seite @JoinColumn(name= "address") private Address address; @Entity public class Address implements Serializable { @Id @GeneratedValue(strategy= GenerationType.AUTO) private Long id; private String city; private String street; private int houseNumber; Inverse Seite @OneToOne(mappedBy= "address") private Person person; Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 35 1:1-Relation bidirektional III em.getTransaction().begin(); address= new Address("Berlin","Gustavzeile",3); person= new Person("Heidi", "Musterfrau",address); address.setPerson(person); em.persist(person); System.out.println(person); address= new Address("Fürstenwalde","Karl-May-Allee",23); person= new Person("Gudrun", "von Oiouten",address); address.setPerson(person); em.persist(person); System.out.println(person); em.getTransaction().commit(); Bidirektional bezieht sich auf die Zeigerstruktur, d.h. auf beiden Seiten muss es jeweils ein Attribut in der Klasse geben, in dem auf das jeweilige andere Objekt verwiesen wird. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 36 1:1-Relation bidirektional IV - Schemata Klasse Person Klasse Address Aufgrund der Relationen ändert sich an der Tabellenstruktur im Vergleich zu unidirektionalen Beziehung nichts(!). 37 Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 1:N-Relation unidirektional I Person Address 1 1 Account 1 1..* Die 1:N-Relation wird durch eine eigene Tabelle realisiert. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 Ebene der Objekte Ebene der Tabellen 38 1:N-Relation unidirektional II @Entity public class Account implements Serializable{ @Id @GeneratedValue(strategy= GenerationType.AUTO) private Long id; private long number; private double bankBalance; public Account() {} public Account(long number, double bankBalance) { this.number= number; this.bankBalance= bankBalance; } @Entity public class Person implements Serializable { @Id @GeneratedValue(strategy= GenerationType.AUTO) private Long id; private String firstName; private String lastName; @OneToOne(cascade= CascadeType.ALL) @JoinColumn(name= "address") private Address address; @OneToMany(cascade= CascadeType.ALL) private Set<Account> accounts; Ebene der Objekte: Set Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 39 1:N-Relation unidirektional III - Hauptprogramm em.getTransaction().begin(); address= new Address("Berlin","Gustavzeile",3); person= new Person("Heidi", "Musterfrau",address); address.setPerson(person); em.persist(person); account= new Account(42,0.0); Zwei Konten person.getAccounts().add(account); werden einaccount= new Account(100,7450.80); gerichtet person.getAccounts().add(account); address= new Address("Fürstenwalde","Karl-May-Allee",23); person= new Person("Gudrun", "von Oiouten",address); address.setPerson(person); em.persist(person); Ein Konto account= new Account(5678,0.0); wird einperson.getAccounts().add(account); gerichtet em.getTransaction().commit(); Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 40 1:N-Relation unidirektional IV - Schemata Klasse Person Klasse Account Tabelle person_account Diese Tabelle realisiert die 1:N-Beziehung. 41 Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 1:N-Relation unidirektional V - Tabelleninhalte Klasse Person Klasse Account Tabelle person_account Heidi hat zwei Konten, während Gudrun nur eines hat. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 42 1:N-Relation bidirektional I Person Address 1 Account 1 1 Inverse Seite 1..* Besitzende Seite Die 1:N-Relation wird nun ohne extra Tabelle mit einem Fremdschlüssel realisiert. Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 Ebene der Objekte Ebene der Tabellen 43 1:N-Relation bidirektional II @Entity public class Account implements Serializable{ @Id @GeneratedValue(strategy= GenerationType.AUTO) private Long id; private long number; private double bankBalance; @ManyToOne(optional= false) @JoinColumn(name= "person",nullable= false) private Person person; @Entity public class Person implements Serializable { @Id @GeneratedValue(strategy= GenerationType.AUTO) private Long id; private String firstName; private String lastName; @OneToOne(cascade= CascadeType.ALL) @JoinColumn(name= "address") private Address address; @OneToMany(mappedBy= "person",cascade= CascadeType.ALL, orphanRemoval= true) private Set<Account> accounts; Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 44 1:N-Relation bidirektional III - Hauptprogramm em.getTransaction().begin(); address= new Address("Berlin","Gustavzeile",3); person= new Person("Heidi", "Musterfrau",address); address.setPerson(person); em.persist(person); account= new Account(42,0.0); person.getAccounts().add(account); account.setPerson(person); account= new Account(100,7450.80); Rückverkettungen person.getAccounts().add(account); account.setPerson(person); address= new Address("Fürstenwalde","Karl-May-Allee",23); person= new Person("Gudrun", "von Oiouten",address); address.setPerson(person); em.persist(person); account= new Account(5678,0.0); person.getAccounts().add(account); account.setPerson(person); Rückverkettung em.getTransaction().commit(); 45 Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 1:N-Relation bidirektional IV - Schemata Klasse Person Klasse Account Tabelle Person Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 Tabelle Account 46 Nach dieser Anstrengung etwas Entspannung... Komponenten – WS 2014/15 – Teil 14/Hibernate-JPA1 47