635-0.book Seite 17 Montag, 6. August 2007 12:52 12 Wir starten mit einem Beispiel, in dem wir eine Anwendung für ein Datenbank konfigurieren, Daten erzeugen, löschen und abfragen und das objektorientierte Konzept der Assoziation verwenden. Danach werden wir uns die Bausteine und drei unterschiedliche Status von Hibernate ansehen, bevor es um das Speichern, Bearbeiten, Löschen und Ändern von Objekten geht. Am Ende des Kapitels erstellen wir dann bereits die erste Webanwendung. 1 Einführung in Hibernate Für jede richtige Anwendung benötigt man einen Anwendungsfall. Daher starten wir mit einem ersten Beispiel. 1.1 Erstes Hibernate-Beispiel Unser Anwendungsfall ist süß. Wir untersuchen die Honigproduktion. Bekanntermaßen benötigen wir viele Bienen, um ein Glas Honig zu erzeugen. Um das darzustellen, sind zwei Klassen erforderlich: Honig (engl. honey) und Biene (engl. bee). Bienen und Honig stehen in einer 1:n-Beziehung oder – wenn wir eine objektorientierte Terminologie verwenden möchten – es gibt eine 1:n-Assoziation zwischen der Klasse Biene und Honig. Folgende Abbildung zeigt uns das entsprechende Klassendiagramm. Honey -id : Integer -name : String -taste : String -bees : Bee = new HashSet<Bee>() honey 1 Bee -id : Integer -name : String -honey : Honey bees * Abbildung 1.1 Ein Klassendiagramm Die beiden Objekte werden wir in den unten gezeigten Tabellen in der Datenbank speichern: 17 635-0.book Seite 18 Montag, 6. August 2007 12:52 12 1 Einführung in Hibernate + id name taste honey int4 Nullable = false varchar(255) Nullable = true varchar(255) Nullable = true + id name # honey_id bee int4 varchar(255) int4 Nullable = false Nullable = true Nullable = true Abbildung 1.2 Tabellen in Hibernate Damit wir eine vollständige Hibernate-Anwendung erhalten, müssen wir mehrere Schritte unternehmen: 왘 Klassen erstellen 왘 Mapping zu den Datenbanktabellen festlegen, in denen Honig und Biene gespeichert werden 왘 Hibernate-Libraries konfigurieren 왘 Hibernate konfigurieren mitsamt Datenquelle 왘 Quellcode schreiben, der unsere Klassen verwendet Quellcode Für dieses Beispiel finden Sie den gesamten Quellcode im Buch und auf der Webseite zum Buch: http://www.galileocomputing.de/978. Für alle anderen Beispiele werden im Buch nur Ausschnitte gezeigt. Das Projekt heißt FirstHibernateExample bzw. FirstAnnotationExample für die Annotation-Variante. 1.1.1 Projekt und Klassen erstellen Java-Projekt erstellen Erstellen Sie in Ihrer Entwicklungsumgebung ein neues Java-Projekt. Ich habe Eclipse verwendet, aber es gibt natürlich auch sehr gute andere IDEs. Ich habe das Projekt FirstHibernateExample genannt. Klassen erstellen Wir benötigen eine Klasse mit Namen Honey im Package de.laliluna.example. Unsere Klasse hat vier Attribute: Integer id – eine Zahl als Primärschlüssel String name – Name des Honigs String taste – Beschreibung des Geschmacks java.util.Set<Bee> bees – Bienen, die den Honig erzeugt haben 18 635-0.book Seite 19 Montag, 6. August 2007 12:52 12 Erstes Hibernate-Beispiel Ferner brauchen wir: 왘 die Getter und Setter für unsere Attribute. Diese kann man in Eclipse im Kontextmenü 폷 Source 폷 Generate Getter and Setter erzeugen. 왘 einen parameterlosen Konstruktor 왘 die Implementierung von Serializable 왘 eine toString-Methode. Diese verwenden wir für Debug-Ausgaben. So sollte der Quellcode aussehen: package de.laliluna.example; import java.io.Serializable; import java.text.MessageFormat; import java.util.HashSet; import java.util.Set; public class Honey implements Serializable { private Integer id; private String name; private String taste; private Set<Bee> bees = new HashSet<Bee>(); public Honey() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getTaste() { return taste; } public void setTaste(String taste) { this.taste = taste; } public Set<Bee> getBees() { return bees; } public void setBees(Set<Bee> bees) { 19 1.1 635-0.book Seite 20 Montag, 6. August 2007 12:52 12 1 Einführung in Hibernate this.bees = bees; } public String toString() { return MessageFormat.format("Honey: {0} {1} {2}", new Object[]{id, name, taste}); } } Bitte nicht vergessen Damit eine gemappte Klasse korrekt funktioniert, denken Sie bitte immer an die folgenden Punkte: 왘 Serializable implementieren 왘 Konstruktor ohne Parameter (Defaultkonstruktor) 왘 eine ordentliche toString-Methode Hinweis zur Java-Version Ich habe Java Generics verwendet, die mit Java Version 5 eingeführt worden sind. Wenn Sie Java 1.4 nutzen müssen, löschen Sie Dinge wie <Bee> und die Annotation, die es manchmal gibt. Annotations beginnen mit @. Jetzt müssen wir die Klasse Bienen (Bee) mit folgenden Attributen erstellen: 왘 Integer id 왘 String name 왘 Honey honey Das Attribut id ist wiederum der Primärschlüssel und die anderen Attribute sind Merkmale der Klasse. Hier gilt der gleiche »Bitte nicht vergessen«-Hinweis, den ich bei der vorherigen Klasse gegeben habe. Unsere Klasse: package de.laliluna.example; import java.io.Serializable; import java.text.MessageFormat; public class Bee implements Serializable { private Integer id; private String name; private Honey honey; public Bee() { } 20 635-0.book Seite 21 Montag, 6. August 2007 12:52 12 Erstes Hibernate-Beispiel public Bee(String name) { this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Honey getHoney() { return honey; } public void setHoney(Honey honey) { this.honey = honey; } public String toString() { return MessageFormat.format("{0}: id={1}, name={2}", new Object[] { getClass().getSimpleName(), id, name }); } } 1.1.2 Hibernate-Konfiguration In der Konfiguration legen wir folgende Dinge fest: 왘 die Datenbank, mit der wir uns verbinden 왘 den Typ der Datenbank (MySQL, PostgreSQL, Oracle ...) 왘 die Konfigurationseinstellungen für Hibernate 왘 die Klassen bzw. XML Mappings, die eingelesen werden Legen Sie bitte eine Datei hibernate.cfg.xml im src-Verzeichnis an. Und jetzt die große Frage: Annotation oder XML Mapping? Wir haben zwei Möglichkeiten, das Mapping der Klassen auf Tabellen festzulegen: Annotation und XML. Ich diskutiere die Unterschiede in Abschnitt 4.1, »Mapping mit Annotation oder XML«, im Detail. Hier treffen wir einfach eine schnelle Entscheidung. 21 1.1 635-0.book Seite 22 Montag, 6. August 2007 12:52 12 1 Einführung in Hibernate Wenn Sie Java 5 oder neuer verwenden, empfehle ich, Annotations zu verwenden, wenn nicht, haben Sie keine andere Wahl als XML. Ich zeige Ihnen jetzt eine Annotation-Version für PostgreSQL und gebe dann die Hinweise, was man ändern muss, um die Konfiguration für andere Datenbanken oder XML Mappings anzupassen. <?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration3.0.dtd"> <hibernate-configuration> <session-factory> <property name="connection.url"> jdbc:postgresql://localhost:5432/learninghibernate </property> <property name="connection.username">postgres</property> <property name="connection.password">p</property> <property name="connection.driver_class"> org.postgresql.Driver </property> <property name="dialect"> org.hibernate.dialect.PostgreSQLDialect </property> <property name="cache.provider_class"> org.hibernate.cache.EhCacheProvider </property> <property name="current_session_context_class">thread </property> <property name="hibernate.transaction.factory_class"> org.hibernate.transaction.JDBCTransactionFactory </property> <property name="hibernate.hbm2ddl.auto">create</property> <mapping class="de.laliluna.example.Honey" /> <mapping class="de.laliluna.example.Bee" /> </session-factory> </hibernate-configuration> Die Konfiguration enthält zu Beginn die Datenbankverbindung. Anschließend legen wir mit der Einstellung dialect fest, in welchen SQL-Dialekt Hibernate Abfragen übersetzt. Dann konfigurieren wir den Cache und wie sich die Session verhalten soll. hbm2ddl.auto stellt sicher, dass Hibernate unsere Tabellen erstellt. 22 635-0.book Seite 23 Montag, 6. August 2007 12:52 12 Erstes Hibernate-Beispiel Schließlich legen wir fest, welche Mappings gelesen werden. Wenn wir Annotations verwenden, referenzieren wir die Klassen mit <mapping class>. Andere Datenbanken Eine Konfiguration für MySQL benötigt folgende Anpassungen: <property name="connection.url"> jdbc:mysql://localhost/learninghibernate </property> <property name="connection.username">root</property> <property name="connection.password">r</property> <property name="connection.driver_class"> com.mysql.jdbc.Driver </property> <property name="dialect"> org.hibernate.dialect.MySQLDialect </property> Wenn Sie eine andere Datenbank verwenden möchten, müssen Sie die Einstellung connection.url und dialect anpassen. Werfen Sie einen Blick in das JavaPackage org.hibernate.dialect der hibernate.jar. Diese finden Sie im Download von Hibernate. Dort finden Sie alle existierenden Dialekte. Hier ein Auszug aus den unterstützten Dialekten: MySQL5Dialect, OracleDialect, SybaseDialect, SQLServerDialect, HSQLDialect, DerbyDialect. Die connection.url entspricht der URL einer normalen JDBC-Verbindung. XML Mapping statt Annotation Das XML Mapping erfolgt in XML-Dateien. Wir müssen nur eine kleine Änderung an der Konfiguration vornehmen, und zwar die beiden <mapping>-Tags ersetzen. <mapping resource="de/laliluna/example/Honey.hbm.xml" /> <mapping resource="de/laliluna/example/Bee.hbm.xml" /> 1.1.3 Mapping Mit dem Mapping legen wir fest, welcher Tabelle und welcher Spalte ein Attribut einer Klasse zugeordnet ist. Wie oben bereits erwähnt, gibt es zwei Möglichkeiten: Annotation und XML. 23 1.1 635-0.book Seite 24 Montag, 6. August 2007 12:52 12 1 Einführung in Hibernate Annotation Mapping Eine Annotation ist immer an dem @ zu erkennen. Damit können wir im Quellcode Hinweise geben. Fügen Sie einfach die folgenden Annotations hinzu. import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.SequenceGenerator; @Entity @SequenceGenerator(name = "honey_seq", sequenceName = "honey_id_seq") public class Honey implements Serializable { @Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="honey_seq") private Integer id; private String name; private String taste; @OneToMany(mappedBy="honey") private Set<Bee> bees = new HashSet<Bee>(); Mit der Annotation @Entity wird bestimmt, dass unsere Klasse gemappt ist. Den Primärschlüssel kennzeichnen wir mit @Id. Unser Primärschlüssel verwendet eine Datenbanksequenz. Diese muss mit Hilfe von @SequenceGenerator definiert werden, bevor wir beim Attribut id festlegen können, dass es von diesem »Generator« erzeugt wird (@GeneratedValue). Die Annotation @OneToMany beschreibt, dass unser Attribut bees in einer 1:nBeziehung zur Klasse Bee steht. Ich möchte die genaue Festlegung der Fremdschlüsselbeziehung zwischen den Tabellen in der Klasse Bee vornehmen. Daher habe ich den Parameter mappedBy verwendet. Die Klasse Bee hat zwei weitere Annotations. @ManyToOne beschreibt die Beziehung aus Sicht der Klasse Bee. Mit der Annotation @JoinColumn legen wir fest, dass in der Tabelle bee eine Fremdschlüsselspalte existiert. Da wir keinen Namen festlegen, verwendet Hibernate die Spalte honey_id. import import import import import import 24 javax.persistence.Entity; javax.persistence.GeneratedValue; javax.persistence.GenerationType; javax.persistence.Id; javax.persistence.JoinColumn; javax.persistence.ManyToOne; 635-0.book Seite 25 Montag, 6. August 2007 12:52 12 Erstes Hibernate-Beispiel import javax.persistence.SequenceGenerator; @Entity public class Bee implements Serializable { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bee_gen") @SequenceGenerator(name = "bee_gen", sequenceName = "bee_id_seq") private Integer id; private String name; @ManyToOne @JoinColumn private Honey honey; Andere Datenbanken Nicht alle Datenbanken unterstützen Sequenzen, um Werte für den Primärschlüssel zu erzeugen. Für die meisten Datenbanken sollte aber folgende Annotation funktionieren: @Id @GeneratedValue(strategy=GenerationType.AUTO) private Integer id; Sie wählt einen Generator abhängig vom konfigurierten Datenbank-Dialekt. XML Mapping Sie benötigen XML Mappings nur, wenn Sie keine Annotations verwenden. In der Mapping-Datei wird die Zuordnung zwischen Attributen und Datenbankspalten festgelegt. Legen Sie die Datei Honey.hbm.xml im Package de.laliluna.example an. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping3.0.dtd" > <hibernate-mapping package="de.laliluna.example"> <class name="Honey" table="thoney" > <id name="id" column="id"> <generator class="sequence"> <param name="sequence">honey_id_seq</param> </generator> </id> <property name="name" type="string"></property> <property name="taste" type="string"></property> 25 1.1 635-0.book Seite 26 Montag, 6. August 2007 12:52 12 1 Einführung in Hibernate <set name="bees" inverse="true"> <key column="honey_id"></key> <one-to-many class="Honey" /> </set> </class> </hibernate-mapping> Mit <id> wird der Primärschlüssel beschrieben und wie dieser erzeugt wird. Die »normalen« Attribute werden mit Hilfe von <property> den Tabellenspalten zugeordnet. Wenn wir keinen Namen für die Tabellenspalte angeben (column=«xyz«), wird der Name des Attributs übernommen. <set> beschreibt die Beziehung zur Klasse Bee. Da wiederum nicht alle Datenbanken Sequenzen unterstützen, können wir Hibernate anhand des Datenbankdialekts einen Generator auswählen lassen. Ändern Sie das Tag für den Generator wie folgt: <generator class="native"/> Wenn auch das nicht funktioniert, dann müssen Sie einen Blick in Abschnitt 4.4, »Mapping von Primärschlüsseln«, werden. Für die Klasse Bee legen Sie eine Datei Bee.hbm.xml im gleichen Java-Package an. Als weiteres Tag wird hier <many-to-one> verwendet. Damit wird die Beziehung aus Sicht der Klasse Bee beschrieben. Die assoziierte Klasse Honey müssen wir nicht angeben. Sie wird von Hibernate aus dem Attribut der Java-Klasse ermittelt. <class name="Bee" table="tbee" > <id name="id" > <generator class="sequence"> <param name="sequence" >bee_id_seq</param> </generator> </id> <property name="name" type="string"></property> <many-to-one name="honey"></many-to-one> </class> Warum type="string" und nicht type="java.lang.String"? Wir können im Mapping auch Java-Klassen angeben. Hibernate-Typen sind aber präziser. Es gibt zum Beispiel die Typen date, timestamp und time, aber nur eine einzige Java-Klasse java.util.Date. Hibernate müsste raten, welchen Typen Sie meinen. Daher empfehle ich, immer die Hibernate-Typen zu verwenden. Generell ist die Angabe von type nur notwendig, wenn Hibernate den Typ nicht erraten kann. Bei java.util.String könnten wir uns die type-Angabe auch sparen. 26 635-0.book Seite 27 Montag, 6. August 2007 12:52 12 Erstes Hibernate-Beispiel 1.1.4 Notwendige Bibliotheken Um Hibernate zu verwenden, benötigen wir einige Bibliotheken, alias JARDateien. Bei Hibernate hat von 2006 nach 2007 eine Explosion von Teilprojekten stattgefunden. Erschrecken Sie sich nicht, wenn Sie Hibernate auf http://www.hibernate.org/ herunterladen. Wir benötigen nur Hibernate Core und – wenn Sie Annotations verwenden – Hibernate Annotations. Entpacken Sie die Datei. Im Hauptverzeichnis finden Sie die hibernate.jar. Im libVerzeichnis liegen eine Menge weiterer Bibliotheken, die wir aber nicht alle benötigen. In der Datei README.txt ist erläutert, was wofür erforderlich ist. Folgende Bibliotheken benötigen wir für unser Beispiel: 왘 log4j.jar 왘 hibernate3.jar 왘 ant-antlr-1.6.5.jar 왘 asm.jar 왘 commons-logging-1.0.4.jar 왘 antlr-2.7.6.jar 왘 cglib-2.1.3.jar 왘 dom4j-1.6.1.jar 왘 jdbc2_0-stdext.jar 왘 asm-attrs.jar 왘 commons-collections-2.1.1.jar 왘 ehcache-1.2.3.jar 왘 jta.jar In Eclipse kann man diese in den Project Properties zum Java Build Path hinzufügen. Der Dialog heißt: Add External Jars. Um Annotations nutzen zu können, muss man Hibernate Annotations herunterladen. Wir benötigen folgende Bibliotheken: 왘 ejb3-persistence.jar 왘 hibernate-annotations.jar 왘 hibernate-commons-annotations.jar 27 1.1 635-0.book Seite 28 Montag, 6. August 2007 12:52 12 1 Einführung in Hibernate Datenbanktreiber Natürlich ist auch ein JDBC-Datenbanktreiber notwendig. Die entsprechende Bibliothek muss noch besorgt werden. Für PostgreSQL bekommt man diesen auf http://jdbc.postgresql.org. Der JDBC 3-Treiber ist in Ordnung, wenn Sie Java 1.4 oder neuer verwenden. Den MySQL Connector findet man auf: http://www.mysql.com/products/connector/j/ Einen Oracle-Datenbank-Treiber erhält man bei Oracle: http://www.oracle.com 1.1.5 Session-Factory erstellen Eine Session-Factory ist sehr wichtig für Hibernate. Wie der Name sagt, ist diese Klasse eine Fabrik, mit der wir Hibernate-Sessions erzeugen können, wann immer wir diese benötigen. Eine Session spielt eine zentrale Rolle in Hibernate. Mit Hilfe einer Session können wir Daten speichern, löschen und die Datenbank abfragen. Die Session-Factory initialisiert auch die Hibernate-Konfiguration. Wir müssen wieder Annotation und XML unterscheiden. Hier zuerst die Annotation-Version. Von der Session-Factory sollte es nur eine Instanz geben (Singleton Pattern). Genau dies stellt die folgende Klasse sicher. package de.laliluna.hibernate; import org.hibernate.SessionFactory; import org.hibernate.cfg.AnnotationConfiguration; import org.hibernate.cfg.Configuration; public class InitSessionFactory { /** The single instance of hibernate SessionFactory */ private static org.hibernate.SessionFactory sessionFactory; private InitSessionFactory() { } static { final AnnotationConfiguration cfg = new AnnotationConfiguration(); cfg.configure("/hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static SessionFactory getInstance() { return sessionFactory; } } 28 635-0.book Seite 29 Montag, 6. August 2007 12:52 12 Erstes Hibernate-Beispiel Wenn wir XML Mapping einsetzen, können wir folgende Zeile statt AnnotationConfiguration verwenden. final Configuration cfg = new Configuration(); Eine AnnotationConfiguration unterstützt allerdings neben Annotation auch XML Mappings. Sie können diese sogar bunt mischen. 1.1.6 Logging Konfiguration Hibernate verwendet log4j für die Logausgabe. Oben haben wir ja die Bibliothek hinzugefügt. Log4j erwartet eine Konfigurationsdatei im Hauptverzeichnis, sonst werden wir später mit folgender Nachricht begrüßt: log4j:WARN No appenders could be found for logger (TestClient). log4j:WARN Please initialize the log4j system properly. Legen Sie eine Datei mit dem Namen log4j.properties im src-Verzeichnis an. Wir konfigurieren eine einfache Logausgabe in der Console. ### direct log messages to stdout ### log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n # Achtung, die vorherige Zeile wird versehendlich umgebrochen log4j.rootLogger=debug, stdout log4j.logger.org.hibernate=info #log4j.logger.org.hibernate=debug ### log just the SQL log4j.logger.org.hibernate.SQL=debug 1.1.7 Datenbank und Tabellen erstellen Erstellen Sie eine Datenbank und achten Sie darauf, den gleichen Namen zu wählen, den wir in der connection.url der Hibernate-Konfiguration verwendet haben. In der hibernate.cfg.xml haben wir festgelegt, dass die Tabellen von Hibernate bei der Initialisierung erstellt werden, daher müssen wir das nicht von Hand erledigen. <property name="hibernate.hbm2ddl.auto">create</property> Die Einstellung create löscht die Tabellen und erstellt diese neu. Sie eignet sich daher nur für die Entwicklung. Alternativ kann man die Einstellung update verwenden. Hibernate versucht dann, die Tabellen nur bei Bedarf zu erzeugen bzw. 29 1.1 635-0.book Seite 30 Montag, 6. August 2007 12:52 12 1 Einführung in Hibernate zu ändern. Das klappt nahezu immer. Ich würde es für den Live-Betrieb dennoch ausschalten. 1.1.8 Testen Wir werden eine einfache Klasse erstellen, die unser Mapping verwendet, zudem Daten erstellt, löscht und abfragt. Ich gebe Ihnen jetzt noch ein paar Hinweise und schlage vor, dass Sie sich dann den Quellcode der Klasse ansehen. Wenn Sie Hibernate verwenden, müssen Sie immer ein paar Dinge beachten: 왘 Sie brauchen immer eine Session, um auf Daten zuzugreifen. 왘 Der Datenzugriff muss immer innerhalb einer Transaktion erfolgen. 왘 Wenn etwas schief geht, müssen Sie die Transaktion zurückrollen. Schauen Sie sich den folgenden Quellcode an, lassen Sie ihn einfach mal durchlaufen und prüfen Sie, was in der Datenbank passiert. In der Methode main rufen wir verschiedene Methoden auf, die Daten anlegen, ändern, löschen oder anzeigen. Wenn in einer Methode eine Exception auftritt, stellen wir sicher, dass unsere Transaktion zurückgerollt und die Hibernate-Session geschlossen wird. package de.laliluna.example; import java.util.Iterator; import java.util.List; import org.apache.log4j.Logger; import org.hibernate.*; import org.hibernate.transform.DistinctRootEntityResultTransformer; import de.laliluna.hibernate.InitSessionFactory; public class TestExample { private static Logger log = Logger.getLogger(TestExample.class); public static void main(String[] args) { try { clean(); createHoney(); createRelation(); delete(); update(); query(); initBees(); } catch (RuntimeException e) { try { Session session = InitSessionFactory.getInstance() .getCurrentSession(); 30 635-0.book Seite 31 Montag, 6. August 2007 12:52 12 Erstes Hibernate-Beispiel if (session.getTransaction().isActive()) session.getTransaction().rollback(); } catch (HibernateException e1) { log.error("Error rolling back transaction"); } // throw the exception again throw e; } } Die Methode createHoney legt ein neues Objekt an und speichert es durch den Aufruf von session.save in der Datenbank. private static Honey createHoney() { Honey forestHoney = new Honey(); forestHoney.setName("forest honey"); forestHoney.setTaste("very sweet"); Session session = InitSessionFactory.getInstance() .getCurrentSession(); Transaction tx = session.beginTransaction(); session.save(forestHoney); tx.commit(); return forestHoney; } Die Methode update lässt ein Objekt erzeugen, ändert dann den Namen und aktualisiert das geänderte Objekt mit session.update. private static void update() { Honey honey = createHoney(); Session session = InitSessionFactory.getInstance() .getCurrentSession(); Transaction tx = session.beginTransaction(); honey.setName("Modern style"); session.update(honey); tx.commit(); } Die Methode delete erzeugt gleichfalls ein Objekt und löscht es mit dem Aufruf von session.delete sofort wieder. private static void delete() { Honey honey = createHoney(); Transaction tx = null; Session session = InitSessionFactory.getInstance() .getCurrentSession(); 31 1.1 635-0.book Seite 32 Montag, 6. August 2007 12:52 12 1 Einführung in Hibernate Transaction tx = session.beginTransaction(); session.delete(honey); tx.commit(); } Die Tabellen werden von der Methode clean gesäubert. Die Methode session.createQuery erzeugt eine Abfrage, die durch executeUpdate ausgeführt wird. private static void clean() { Session session = InitSessionFactory.getInstance() .getCurrentSession(); Transaction tx = session.beginTransaction(); session.createQuery("delete from Bee").executeUpdate(); session.createQuery("delete from Honey").executeUpdate(); session.flush(); session.clear(); tx.commit(); } Die Methode createRelation zeigt uns, wie man Objekte erzeugt und die Beziehung zwischen den Objekten setzt. In der Datenbank wird eine Fremdschlüsselbeziehung erstellt. private static void createRelation() { Session session = InitSessionFactory.getInstance() .getCurrentSession(); Transaction tx = session.beginTransaction(); Honey honey = new Honey(); honey.setName("country honey"); honey.setTaste("Delicious"); session.save(honey); Bee bee = new Bee("Sebastian"); session.save(bee); /* Wir setzen die Beziehung auf BEIDEN Seiten */ bee.setHoney(honey); honey.getBees().add(bee); tx.commit(); } Zum Schluss sehen wir ein einfaches Beispiel, wie Daten mit der Hibernate Query Language (HQL) abgefragt werden. Die Abfrage wird wiederum mit session.createQuery erzeugt. Diesmal rufen wir aber die Methode list auf, da wir ein Ergebnis erwarten. 32 635-0.book Seite 33 Montag, 6. August 2007 12:52 12 Erstes Hibernate-Beispiel private static void query() { Session session = InitSessionFactory.getInstance() .getCurrentSession(); Transaction tx = session.beginTransaction(); List honeys = session.createQuery ("select h from Honey as h").list(); for (Iterator iter = honeys.iterator(); iter.hasNext();) { Honey element = (Honey) iter.next(); log.debug(element); } tx.commit(); } } Das ging jetzt sehr schnell, aber Sie müssen nicht alles auf Anhieb im Detail verstehen. Das sollten Sie gelernt haben Erstellen einer Klasse, die gemappt werden soll. Wichtig waren ein parameterloser Default-Konstruktor, Serializable Interface, eine hübsche toString-Methode. 왘 Einfaches Mapping 왘 Speichern und Löschen von Objekten 왘 Eine einfache Abfrage Warum gibt es den Try- und Catch-Block? 왘 Manche Datenbanken halten Ressourcen offen, wenn eine Transaktion nicht mit commit oder rollback beendet wird. Deswegen gibt es das rollback nach einer Exception. 왘 Nach einer Exception ist die aktuelle Session unbrauchbar. Wir müssen diese schließen. In dem Beispiel verwenden wir die Einstellung current_session_ context="thread". Dieser Sessionkontext stellt sicher, dass beim Aufruf von commit oder rollback die Session geschlossen wird. Sonst müssen wir die Methode session.close() aufrufen. 왘 In einer normalen Anwendung reicht uns eine Stelle aus, die Transaktionen zurückrollt und die Session – wenn erforderlich – schließt. 왘 Das könnten wir in einem Exception Handler von Struts, einem Interceptor von MyFaces oder in einem Servletfilter vornehmen. Unser Quellcode sieht also gewöhnlich einfach so aus: Session session = InitSessionFactory.getInstance().getCurrentSession(); Transaction tx = session.beginTransaction(); 33 1.1 635-0.book Seite 34 Montag, 6. August 2007 12:52 12 1 Einführung in Hibernate session.save(honey); tx.commit(); Müssen wir unsere Session nicht schließen? Richtig, wir müssen eine Session immer schließen. Ich finde es praktisch, wenn die Session automatisch nach einem commit oder rollback geschlossen wird. Der currentSessionContext thread, den wir in der Hibernate-Konfiguration eingestellt haben, stellt das für uns sicher. Wir brauchen daher nicht explizit ein session.close aufzurufen. Deployment im JBoss Application-Server Wenn Sie versuchen, dieses Beispiel oder eins der späteren im JBoss Application-Server zu deployen, können Sie auf Schwierigkeiten stoßen. JBoss bringt von Haus aus bereits Hibernate mit. Leider führt das zu unerwartetem Verhalten in den Beispielen, da ich in der Hibernate-Konfiguration festlege, wie sich die Session verhalten soll, und innerhalb vom JBoss dies teilweise ignoriert wird. Entweder verwenden Sie vorerst den Tomcat Application-Server bzw. eine andere Servlet Engine oder Sie werfen bereits jetzt einen Blick in die Abschnitte 2.3.2 und 5.4, »JTA versus JDBC Transaktionen«. 1.1.9 MyEclipse Tools für die Hibernate-Entwicklung Es gibt zahlreiche Tools für die Entwicklung von Hibernate. Ich verwende MyEclipse, ein Plugin für Eclipse. MyEclipse wird von Genuitec angeboten: http://www.myeclipseide.com/. Ich verwende es für die Entwicklung von Webanwendungen. Es bietet Unterstützung für Spring, Hibernate, EJB, Struts, MyFaces und ist gewöhnlich auf einem recht aktuellen Stand. Hibernate-Unterstützung aktivieren Mit MyEclipse können Sie Webprojekte direkt anlegen. Klickt man im Package View mit der rechten Maustaste auf ein Projekt, kann man die Hibernate-Unterstützung im Kontextmenü aktivieren. Abbildung 1.3 Hibernate-Unterstützung im Kontextmenü aktivieren 34 635-0.book Seite 35 Montag, 6. August 2007 12:52 12 Erstes Hibernate-Beispiel Mit Hilfe des Wizards kann man: 왘 Bibliotheken hinzufügen 왘 Hibernate-Konfiguration erstellen 왘 Session-Factory erzeugen lassen Über den Wizard kann man auch ein Connection Profile anlegen. Es konfiguriert eine Datenbankverbindung mit Datenbanktreiber, Benutzernamen und Dialekt. Abbildung 1.4 Ein Connection Profile erzeugen Reverse Engineering Hibernate liefert eigene Tools zur Erzeugung von Mapping-Dateien und Klassen auf Basis einer existierenden Datenbank. MyEclipse integriert solche Funktionen elegant in Eclipse. Wenn Sie ein Connection Profile angelegt haben, öffnen Sie jetzt den View DB Browser (MyEclipse) (siehe Abbildung 1.5). Öffnen Sie das eben erstellte Connection Profile (siehe Abbildung 1.6). 35 1.1 635-0.book Seite 36 Montag, 6. August 2007 12:52 12 1 Einführung in Hibernate Abbildung 1.5 Der View DB Browser Abbildung 1.6 Das Profil öffnen Wählen Sie die Tabellen aus, aus denen Sie Klassen, Mapping und DAOs erzeugen möchten. Der Wizard bietet Ihnen unterschiedliche Optionen, wie Sie die Tabellen erzeugen können. Probieren Sie einfach die Möglichkeiten aus. 36 635-0.book Seite 37 Montag, 6. August 2007 12:52 12 Hibernate-Grundlagen Abbildung 1.7 Die Auswahl der Tabellen Hinweis DAOs (Data Access Objects), die Sie ebenfalls mit dem Wizard erzeugen können, besprechen wir zu einem späteren Zeitpunkt. 1.1.10 Andere Tools für die Hibernate-Entwicklung Die Entwicklungsumgebungen IntelliJ und Netbeans verfügen von Haus aus über Hibernate-Tools. Auf den Webseiten von Hibernate können Sie sich ferner die Hibernate Tools herunterladen. Das ist ein Plugin für Eclipse. 1.2 Hibernate-Grundlagen Sie wissen bereits, dass Hibernate ein Object-Relation-Mapping Framework ist. Da Hibernate sogar ein besonders mächtiges ORM Framework ist, bietet es eine ganze Menge an Funktionen. 1.2.1 Leistungsfähige Mapping-Varianten Ein Mapping muss nicht immer aus eine Klasse pro Tabelle bestehen. Wir können eine Vielzahl von objektorientierten Konzepten mit Hilfe der Mappings darstellen. Im ersten Beispiel haben Sie eine Beziehung zwischen Klassen kennen gelernt. Das entspricht in der Objektorientierung einer Assoziation. Mit Hibernate kön- 37 1.2 635-0.book Seite 285 Montag, 6. August 2007 12:52 12 In diesem Kapitel werden Sie lernen, wie Sie Hibernate in andere Technologien integrieren und Exceptions mit diesen Technologien behandeln. Ich werde einige Fallstricke nennen, damit Sie möglichst wenig Schwierigkeiten bei der Integration haben werden. 5 Integration anderer Technologien Spring ist ein wunderbares Framework, um die Geschäftslogikschicht zu implementieren. Es kann Hibernate auf elegante Weise integrieren. Spring können Sie auf der Seite http://www.springframework.org/ herunterladen. Dieses Kapitel setzt voraus, dass Sie das Spring Framework kennen, weil ich keine SpringGrundlagen erklären werde. 5.1 Hibernate und Spring Die Dokumentation zu Spring stellt drei Alternativen vor. Ich halte eine von ihnen für nicht sehr schön und werde daher nur zwei Möglichkeiten erläutern. Beide Beispiele verwenden Spring in der Version 2 mit Annotations. Sie benötigen daher Java 5 oder aktueller. Wenn Sie Java 1.4 verwenden, müssen Sie das Transaktionshandling anders konfigurieren. Das werde ich an entsprechender Stelle erläutern. 5.1.1 Konfiguration Sie haben drei Möglichkeiten der Konfiguration von Hibernate, wenn Sie Spring verwenden: 왘 Verweis in der Spring-Konfiguration auf eine Hibernate-Konfigurationsdatei 왘 Vollständige Konfiguration von Hibernate in der Spring-Konfigurationsdatei 왘 Mischen der beiden Ansätze Ich würde die letzte Möglichkeit empfehlen, weil einige Entwicklungsumgebungen Textvervollständigung für Hibernate-Einstellungen in der Hibernate-Konfigurationsdatei anbieten. Nachfolgende Abbildung zeigt das für Eclipse mit dem MyEclipse-Plugin: 285 635-0.book Seite 286 Montag, 6. August 2007 12:52 12 5 Integration anderer Technologien Abbildung 5.1 Textvervollständigung mit dem MyEclipse-Plugin Eine Konfiguration in Spring umfasst eine Datenquelle und eine vom Spring Framework zur Verfügung gestellte Session-Factory. Es ist wichtig, dass wir diese verwenden, sonst funktioniert die Integration nicht ordentlich und es können Connection Leaks auftreten. <bean id="datasource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="org.postgresql.Driver" /> <property name="jdbcUrl" value="jdbc:postgresql://localhost:5432/learninghibernate" /> <property name="user" value="postgres" /> <property name="password" value="p" /> <property name="minPoolSize" value="2" /> <property name="maxPoolSize" value="4" /> </bean> <bean id="hibernateSessionFactory" class="org.springframework.orm.hibernate3.annotation. AnnotationSessionFactoryBean"> <property name="dataSource" ref="datasource" /> <property name="configLocation"> <value>classpath:hibernate.cfg.xml</value> </property> </bean> Die Hibernate-Konfigurationsdatei ist sehr kurz und enthält keine Angaben zur Datenquelle oder zum Verhalten der Session. 286 635-0.book Seite 287 Montag, 6. August 2007 12:52 12 Hibernate und Spring <hibernate-configuration> <session-factory> <property name="dialect"> org.hibernate.dialect.PostgreSQLDialect </property> <property name="cache.provider_class"> org.hibernate.cache.EhCacheProvider </property> <property name="hbm2ddl.auto">none</property> <mapping class="de.laliluna.example.domain.Hedgehog" /> <mapping class="de.laliluna.example.domain.WinterAddress" /> </session-factory> </hibernate-configuration> Problembereich Sie sollten auf keinen Fall die Datenquelle in der Hibernate-Konfigurationsdatei festlegen. Wenn doch, bekommen Sie Probleme, sobald Sie die Transaktionssteuerung von Spring verwenden. Alternativ können Sie alle Einstellungen der Session-Factory vollständig in Spring konfigurieren. <bean id="hibernateSessionFactory" class="org.springframework.orm.hibernate3.annotation. AnnotationSessionFactoryBean"> <property name="dataSource" ref="datasource" /> <property name="annotatedClasses"> <list> <value>de.laliluna.example.domain.Hedgehog</value> <value>de.laliluna.example.domain.WinterAddress</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.hbm2ddl.auto=none hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect hibernate.cache.provider_class=org.hibernate.cache .EhCacheProvider </value> </property> </bean> Unser Beispiel hat eine Session-Factory gezeigt, die Annotations unterstützt. Wenn Sie nur XML Mappings einsetzen, können Sie auch die Klasse org.springframework.orm.hibernate3.LocalSessionFactoryBean auswählen. 287 5.1 635-0.book Seite 288 Montag, 6. August 2007 12:52 12 5 Integration anderer Technologien Warum keine Hibernate-Session-Factory? Es wäre möglich, statt der Spring-Session-Factory unsere Hibernate-Session-Factory aus den bisherigen Beispielen zu verwenden. Dann müssten wir auch eins der in Abschnitt 2.3, »Session und Transaktionen«, vorgestellten Pattern verwenden. Session session = factory.getCurrentSession(); Transaction tx = null; try { tx = session.beginTransaction(); // do some work tx.commit(); } catch (RuntimeException e) { try { if (tx != null) tx.rollback(); } catch (HibernateException e1) { log.error("Transaction roleback not succesful", e1); } throw e; } Spring bietet uns eine Möglichkeit, Transaktionen auf eine komfortablere Art zu steuern. Die Transaktionssteuerung ist übergreifend und kann eine Hibernate- und eine JDBC-Transaktion einschließen. Auf diese elegante Möglichkeit verzichten wir, wenn wir die Hibernate-Session-Factory verwenden. Ich empfehle, lieber die Möglichkeiten von Spring zu verwenden. Dabei müssen Sie aber auf die Nutzung der Hibernate-Transaktionssteuerung verzichten, sonst hat Spring erhebliche Schwierigkeiten, Datenbankverbindungen und Hibernate-Sessions freizugeben. Verwenden Sie also auf keinen Fall session.beginTransaction(), wenn Sie sich für den »Spring Weg« entscheiden. 5.1.2 Verwendung des Spring Templates In diesem Abschnitt werden wir die erste Möglichkeit kennen lernen, Spring und Hibernate zu integrieren. Sie finden eine vollständige Implementierung im Projekt HibernateSpring (http://www.galileocomputing.de/978). Die Bibliotheken können Sie von der Spring Framework-Webseite herunterladen. Sie benötigen die Download-Version mit allen Abhängigkeiten. Das Beispielprojekt verwendet die Bibliotheken: 왘 spring.jar 왘 aspectjrt.jar 왘 spring-aspects.jar 왘 aspectjweaver.jar 288 635-0.book Seite 289 Montag, 6. August 2007 12:52 12 Hibernate und Spring Spring bietet für unterschiedliche Persistenztechnologien ein Template an, das die Implementierung und technologiespezifische Exceptions kapselt und in einheitliche Spring Exceptions umwandelt. Diese Templates existieren für 왘 Hibernate 왘 JDO 왘 Ibatis 왘 Toplink 왘 JPA 왘 JDBC Wenn Sie unterschiedliche Technologien verwenden, ist das Template ein guter Ansatz, um ein einheitliches Verhalten und eine globale Transaktionssteuerung zu erreichen. Zunächst wird ein Hibernate Template erzeugt. Dieses benötigt die vorhin konfigurierte Spring-Session-Factory. HibernateTemplate template = new HibernateTemplate(factory); Das Template wird verwendet, um Hibernate zu kapseln. Das unten stehende Beispiel zeigt die Verwendung. Sie lesen den Quellcode am besten von innen nach außen. return (List<Hedgehog>) template.execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { return session.createCriteria(Hedgehog.class).list(); } }); Mit new HibernateCallback() erzeugen wir eine anonyme Implementierung des Interfaces HibernateCallback. Das Interface hat genau eine Methode: doInHibernate. Die Methode bekommt von Spring als Parameter eine Hibernate-Session übergeben. Die eigentliche Arbeit wird in der Zeile return session.get(Hedgehog.class, id) verrichtet. Wir übergeben der Methode execute des Templates diese frisch erstellte Implementierung zur Ausführung. Diese Konstruktion mag ungewöhnlich erscheinen, ermöglicht aber Spring, die Ausführung des Hibernate-Codes zu kapseln, Ausnahmen umzuwandeln und sicherzustellen, dass, wenn eine Exception im Hibernate-Code auftritt, die Trans- 289 5.1 635-0.book Seite 290 Montag, 6. August 2007 12:52 12 5 Integration anderer Technologien aktion zurückgerollt und die Session geschlossen wird. Bisher mussten Sie das immer selbst erledigen. Der aufmerksame Leser wird sich jetzt vielleicht fragen, welche Transaktion zurückgerollt wird. Schließlich haben wir keine Transaktion begonnen. Wenn wir nicht explizit eine Transaktion festlegen, stellt Spring das Transaktionsverhalten der JDBC-Verbindung auf auto-commit. Jede Abfrage läuft dann in einer Transaktion. Wie das Transaktionsverhalten explizit gesteuert wird, sehen wir uns im übernächsten Abschnitt an. Am Ende noch ein wichtiger Hinweis: Das Hibernate Template bietet für viele Methoden der Hibernate-Session bereits eine fertige Implementierung: save, saveOrUpdate, delete, update und viele mehr. Diese können Sie beruhigt verwenden. template.save(hedgehog); Problembereich des Hibernate Templates Ich rate dringend davon ab, die unterschiedlichen find-Methoden des Hibernate Templates zu verwenden. Das Template hat globale Einstellung für die maximale Anzahl von Zeilen, die eine Abfrage zurückliefert, zum Verhalten in Bezug auf den Cache und diversen anderen Parametern. So können Sie »interessante«, aber unerwartete Ergebnisse erhalten. Schreiben Sie Ihre Abfragen lieber selbst und kapseln Sie diese, wie oben gezeigt, in einem HibernateCallBack. 5.1.3 Alternative zum Spring Template Es gibt sicherlich schöneren Quellcode als das oben gezeigte Template. Ich möchte Ihnen daher eine Alternative zeigen, die auf dieses Konstrukt verzichtet. Wir verwenden die Methode getCurrentSession, die uns auch in der Spring-Session-Factory zur Verfügung steht. Ähnlich wie bei der Verwendung eines CurrentSessionContext rufen wir diese Methode überall auf, wo wir eine HibernateSession benötigen. protected Session getCurrentSession() { return factory.getCurrentSession(); } public void save(Hedgehog object) { getCurrentSession().saveOrUpdate(object); } 290 635-0.book Seite 291 Montag, 6. August 2007 12:52 12 Hibernate und Spring Unser Quellcode funktioniert damit aber noch nicht. Bei diesem Ansatz müssen wir explizit eine Transaktion vor dem Aufruf von getCurrentSession starten. Wie das geht, zeige ich im nächsten Abschnitt. Exception Handling Die Spring-Dokumentation behauptet, dass wir durch diesen Ansatz statt mit einheitlichen Spring Exceptions mit Hibernate Exceptions umgehen müssen. Das ist nicht sehr präzise ausgedrückt. Die Exception wird nur später umgewandelt. Unser erster Ansatz mit Templates wandelt die Exception sofort im Template um. Der zweite Ansatz wandelt die Exception erst im Rahmen der Transaktionssteuerung um. Das bedeutet, dass die Methode in unserer Klasse HedgeHogServiceImp bereits abgearbeitet ist. Da Hibernate Exceptions gewöhnlich fatal sind und erst im FrontendLayer – zum Beispiel in der Webanwendung – behandelt werden, ist uns dieses Verhalten egal. Der zweite Ansatz ist meiner Meinung nach eine gute Wahl, da er besser zu lesen ist und weniger Schreibarbeit erfordert. 5.1.4 Transaktionssteuerung Es ist sehr einfach, Transaktionen mit Spring zu steuern. In der Spring-Konfigurationsdatei müssen Sie nur einen Transaktionsmanager konfigurieren. In diesem Beispiel verwenden wir einen JDBC-basierten Transaktionsmanager. <tx:annotation-driven transaction-manager="txManager"/> <bean id="txManager" class="org.springframework.orm.hibernate3. HibernateTransactionManager"> <property name="sessionFactory" ref="hibernateSessionFactory"/> </bean> Hinweise zur Konfiguration eines JTA-basierten Transaktionsmanagers finden Sie in der Spring-Dokumentation. Die Konfiguration hängt immer vom Application-Server ab. Innerhalb der Anwendung sind Transaktionen leicht zu integrieren. Wir müssen nur die Annotation @Transactional vor eine Methode schreiben. Spring wird dann vor der Methode die Transaktion starten und nach der Methode ein commit aufrufen. @Transactional(propagation = Propagation.REQUIRED) public void update(Hedgehog hedgehog) { hedgehogDao.update(hedgehog); } 291 5.1 635-0.book Seite 292 Montag, 6. August 2007 12:52 12 5 Integration anderer Technologien propagation legt die Art der Transaktion fest. Ich habe eine Übersicht über die Transaktionsarten zusammengestellt. Art der Transaktion Verhalten REQUIRED Eine Transaktion ist erforderlich. Wenn keine Transaktion geöffnet ist, wird eine begonnen und nach der Methode abgeschlossen. Sonst läuft die Methode innerhalb der bestehenden Transaktion. Diese Einstellung ist der Vorgabewert. SUPPORTS Transaktionen werden unterstützt, aber es wird keine Transaktion gestartet. Wenn es eine gibt, läuft die Methode innerhalb der Transaktion, wenn nicht, wird die Methode einfach so aufgerufen. MANDATORY Es ist eine Transaktion erforderlich, aber es wird keine gestartet. Wenn es eine gibt, läuft die Methode innerhalb der Transaktion, wenn nicht, wird eine Exception geworfen. REQUIRES_NEW Es ist eine Transaktion erforderlich und es wird immer eine neue gestartet. Wenn es bereits eine gibt, wird diese pausiert. Nicht alle Transaktionsmanager unterstützen diese Möglichkeit. NOT_SUPPORTED Die Methode kann nicht innerhalb einer Transaktion aufgerufen werden. Wenn eine Transaktion existiert, wird diese pausiert. Nicht alle Transaktionsmanager unterstützen diese Möglichkeit. NEVER Die Methode kann nicht innerhalb einer Transaktion aufgerufen werden. Wenn eine Transaktion existiert, wird eine Exception geworfen. NESTED Es wird eine verschachtelte Transaktion gestartet, also innerhalb einer bestehenden Transaktion wird eine weitere gestartet. Sie können den Transaktionsmanager DataSourceTransactionManager verwenden. Verschachtelte Transaktionen werden allerdings nur von wenigen Datenbanken unterstützt. Manche JTA-Transaktionsmanager unterstützen gleichfalls JTA. Wenn eine Exception vom Typ RuntimeException innerhalb der Methode auftritt, führt Spring automatisch ein Rollback der Transaktion durch. Das gilt nicht für die so genannten Checked Exceptions. Das Rollback-Verhalten können Sie für Checked Exceptions festlegen. Mehr Informationen dazu finden Sie in der Spring-Dokumentation. Tipp: Transaktionskonfiguration auf Klassenebene Sie können die Annotation @Transactional auch vor die Klasse schreiben. Dann verwendet Spring für alle öffentlichen Methoden der Klasse eine Transaktion. Die Annotation auf Klassenebene ist eine Vorgabe für die Methoden. Wenn sich eine Methode anders verhalten soll, so können Sie dort einfach eine weitere Annotation einfügen und den Vorgabewert überschreiben. 292 635-0.book Seite 293 Montag, 6. August 2007 12:52 12 Hibernate und Struts Transaktionen ohne Annotation Die Verwendung von Annotations für die Konfiguration ist meiner Meinung nach eine sehr schöne Lösung. Wenn Sie ein älteres JDK als Version 5 verwenden müssen, stehen Ihnen Annotations leider nicht zur Verfügung. In diesem Fall würde man die Transaktionen entweder explizit im Quellcode steuern oder über die Spring-Konfigurationsdatei festlegen. Mehr Informationen dazu finden Sie in der Spring-Dokumentation. 5.2 Hibernate und Struts Ich habe eine kleine Beispielanwendung geschrieben, die Sie auf der Webseite zum Buch (http://www.galileocomputing.de/978) finden. Diese Anwendung orientiert sich an Best Practices und Sie können sie als hochwertige Vorlage verwenden. Das Projekt heißt Hibernatestruts. Die Anwendung setzt die Ajax-Technologie ein, zeigt eine Liste von Igeln und erlaubt, Igel zu erstellen und zu bearbeiten. Als Datenbank habe ich PostgreSQL eingesetzt. Folgende Bibliotheken habe ich verwendet: 왘 Struts 1.3 왘 Displaytag 1.1 (http://displaytag.sourceforge.net/11/) 왘 Hibernate 3.2 Core und Annotation 왘 Ajaxtags 1.2 (http://ajaxtags.sourceforge.net/) 왘 Prototype Ajax 1.5.0 (http://www.prototypejs.org/) 왘 Script.aculo.us 1.7.0 (http://script.aculo.us/) Die Anwendung demonstriert: 왘 Einsatz von Geschäfts- und DAO-Layer 왘 Verwendung einer generischen Dao-Implementierung 왘 Optimistisches Sperren 왘 Zentraler Umgang mit Exception Ich werde die letzten beiden Punkte im Folgenden im Detail erklären. 5.2.1 Optimistisches Sperren Optimistisches Sperren bedeutet, dass Hibernate prüft, ob ein anderer Benutzer einen Datensatz bereits geändert hat, seitdem die Daten geladen worden sind. 293 5.2 635-0.book Seite 361 Montag, 6. August 2007 12:52 12 Index <array> 203 <bag> 195 <cache> 160 <collection-id> 202 <component> 246 <composite-element> 248 <composite-id> 187, 239, 256 <discriminator> 264 <formula> 239 <generator class=foreign> 216 <idbag> 202 <join table=..> 272 <join> 227 <joined-subclass> 268 <list> 201 <many-to-any> 280 <many-to-many> 226, 229 <many-to-one> 211, 220 <map> 198 <map-key> 198 <meta-value> 280 <one-to-one> 214 <parent> 246 <primitive-array> 204 <set 218 <set> 193, 218 <subclass> 264, 272 <union-subclass> 277 <version 101 @AccessType 345 @AttributeOverride 330 @Basic 328 @BatchSize 117, 342 @Cache 347 @Cascade 338 @Check 347 @CollectionOfElements 203, 247, 252, 334 @Column 328 @ColumnResult 355 @DiscriminatorColumn 263, 344 @DiscriminatorFormula 345 @DiscriminatorValue 344 @Embeddable 245, 248, 334 @Embedded 245, 333 @EmbeddedId 184, 325 @Entity 24, 319 @EntityResult 354 @Enumerated 329 @Fetch 338 @FieldResult 354 @Filter 348 @FilterDef 349 @Filters 348 @Formula 331 @GeneratedValue 24, 324 @GenericGenerator 327 @Id 24, 324 @IdClass 186, 325 @Index 322 @IndexColumn 201, 247, 337 @Indexed Hinweis 222 @Inheritance 263, 267, 343 @JoinColumn 24, 335 @JoinColumns 335 @JoinTable 226, 228, 336 @LazyCollection 342 @LazyToOne 343 @Length 133 @Lob 135, 329 @ManyToMany 228, 333 @ManyToOne 24, 220, 332 @MapKey 198, 339 @MapKeyManyToMany 234 @MappedSuperclass 282, 345 @NamedNativeQueries 129, 352 @NamedNativeQuery 353 @NamedQueries 128, 350 @NamedQuery 128, 350–351 @NotFound 340 @OnDelete 340 @OneToMany 24, 218, 332 @OneToOne 210, 331 @OrderBy 341 @org.hibernate.annotations AccessType 345 BatchSize 342 Cache 347 Cascade 338 Check 347 361 635-0.book Seite 362 Montag, 6. August 2007 12:52 12 Index CollectionOfElements 334 DiscriminatorFormula 345 Entity 319 Fetch 338 Filter 348 FilterDef 349 Filters 348 Formula 331 GenericGenerator 327 Index 322 IndexColumn 337 LazyCollection 342 LazyToOne 343 MapKey 339 NamedQueries 350 NamedQuery 351 OnDelete 340 OrderBy 341 ParamDef 349 Parameter 347 Parent 334 Table 322 Type 346 TypeDef 347 TypeDefs 346 Where 341 @org.hibernate.NotFound 340 @ParamDef 349 @Parameter 347 @Parent 245, 334 @PersistenceContext 308 @PrimaryKeyJoinColumn 216, 336 @PrimaryKeyJoinColumns 337 @QueryHint 128, 351 @SecondaryTable 323 @SequenceGenerator 24, 325 @SqlResultSetMapping 129, 354 @SqlResultSetMappings 353 @Table 322 @TableGenerator 326 @Target 170 @Temporal 329 @TransactionAttribute 308 @Transient 328 @Type 346 @TypeDefs 130, 346–347 @UniqueConstraint 323 @Version 101, 330 @Where 341 362 A Abfragen @NamedQueries 128 @NamedQuery 128 AliasToBeanResultTransformer 109 all 113 any 113 createAlias 111 DetachedCriteria 113 executeUpdate 115 exists 113 gtAll 113 gtSome 114 in 113 left join fetch 118 MatchMode.START 110 Parameter 112 Property.forName 113 Restrictions.disjunction 111 Restrictions.eq 109 Restrictions.in 110 Restrictions.isEmpty 114 Restrictions.like 110 Restrictions.lt 111 Restrictions.or 111 setFetchMode 118 setParameterList 110 setReadOnly 108 some 113 uniqueResult 112 Where-Bedingung mit Beziehungen 111 abstract 176 action = NotFoundAction.IGNORE 340 after_statement 146 after_transaction 146 Aggregation 244 AliasToBeanResultTransformer 109 all 113 allocationSize 124, 325 Annotation 167 AnnotationConfiguration 29 Annotation-Nachteile 169 Annotations an Felder, Methoden und Klassen 170 AnnotationSessionFactoryBean 286 Annotation-Vorteile 168 any 113 appliesTo = tableName 322 635-0.book Seite 363 Montag, 6. August 2007 12:52 12 Index Architektur Geschäftslogik 39 Persistenz Layer 39 Webanwendung 39 Array 190 ArrayList 189–190 Assoziation 17, 37, 188, 244 ASTQueryTranslatorFactory 152 auto-import 172 B Bag 189 batch-size 175 Bibliotheken 27 Bidirektional 205 BinaryType 136 BLOB 134 BLOB Lazy Loading 126 Blob-Felder 149 BlobType 137 bytea 136 Bytecode-Instrumentation 126–127 C C3P0 153 c3p0.* 145 Cache Cache 157 Cache deaktivieren 149 Cache Modus 159 Cache verwenden 161 nonstrict-read-write 159 Query Cache 162 read-only 159 read-write 159 transactional 159–160 cache.provider_class 149 cache.query_cache_factory 149 cache.region_prefix 150 cache.use_minimal_puts 149 cache.use_query_cache 149 cache.use_second_level_cache 149 cache.use_structured_entries 150 CacheConcurrencyStrategy 348 CacheModeType 351 Cache-Regionen 150 Cascading 48, 207 all 208 all-delete-orphan 208 CascadeType 338 CascadeType.ALL 208 CascadeType.DELETE_ORPHAN 208 CascadeType.REFRESH 207 CascadeType.REMOVE 207 delete 208 delete-orphan 208 evict 208 Fehlerpotenzial 209 lock 208 MERGE 208 PERSIST 208 persist 208 REFRESH 208 refresh 208 REMOVE 208 replicate 208 save-update 208 catalog 172, 174 catalog = catalogName 322 cglib.use_reflection_optimizer 144, 152 check 176 class 173 ClassicQueryTranslatorFactory 152 clause = deleted=false 341 clear() 124 CLOB 126, 134 CLOB Lazy Loading 126 Clustering 149 Collection 189 Component Mapping 243 Configuration 29 Connection Pool Connection Pool 152 Connection Pool mit Jboss 156 Connection Pool mit Tomcat 154 connection.autocommit 146 connection.datasource 145 connection.driver_class 145 connection.eineEinstellung 146 connection.isolation 146 connection.password 145 connection.provider_class 146 connection.release_mode 146 connection.url 23, 145 connection.username 145 context.xml 154 363 635-0.book Seite 364 Montag, 6. August 2007 12:52 12 Index createAlias 111 createSQLQuery 115 Criteria Queries 39 Criteria.DISTINCT_ROOT_ENTITY 43 Criteria-Abfragen 104 current_session_context 33 current_session_context_class 147 D DAO DAO Factory 77 Dao mit SessionFactory vs Session 78 DAOs mit Generics 81 DAOs mit Java 1.4 84 Data Access Objects (DAO) 74 Kluge DAO 86 Datenbanktreiber 28 DBCP 153 Debugging 115 default_batch_fetch_size 118, 151 default_catalog 151 default_entity_mode 150 default_schema 150 default-access 172 default-cascade 172 default-lazy 172 DefaultLoadEventListener 132 DerbyDialect 23 detached 44 DetachedCriteria 113 dialect 22, 144 Discriminator Column 38 discriminatorType 263 discriminatorType = DiscriminatorType.STRING 344 DiscriminatorType.CHAR 344 DiscriminatorType.INTEGER 344 DiscriminatorType.STRING 263, 344 discriminator-value 173 dom4j 150 dynamic insert 129, 174, 320 dynamic update 101, 129, 174, 320 dynamic-map 150 E EH Cache 164 EJB 3 301 364 Entity 167, 173 Entity-Manager 301 EntityManagerFactory 303 entity-name 176 EnumType.ORDINAL 329 EnumType.STRING 329 EventListener 132 Eventsystem 132 Exception 30, 89 LazyInitializationException 41, 94 NonUniqueObjectException 50 StaleObjectStateException 51 Exception Handling 68 Exception Handling in JPA 305 Exception Handling mit JSF (MyFaces) 296 Exception Handling mit Spring 292 Exception Handling mit Struts 294 Try und Catch 33 Try und Catch in Webanwendung 68 executeUpdate 115 exists 113 explicit 175 F fetch=FetchType.EAGER 331 FetchMode.JOIN 339 FetchMode.SELECT 339 FetchMode.SUBSELECT 339 find 304 First Level Cache 87 First-Level-Cache 157 flush() 124 FlushMode 97 flushMode 351 FlushModeType.AUTO 351 Foreign Key Constraint 205 format_sql 116 Fremdschlüsselspalte 24 G generate_statistics 151 GeneratedValue 168 GenerationType 168 GenerationType.AUTO 324 GenerationType.IDENTITY 324 GenerationType.SEQUENCE 324 635-0.book Seite 365 Montag, 6. August 2007 12:52 12 Index GenerationType.TABLE 324 Generics 20 getReference 307 Glassfish 310 gtAll 113 gtSome 114 H HashMap 190 HashSet 190 hbm2ddl.auto 22, 150 Hibernate Validator 133 hibernate.cfg.xml 143 hibernate.hbm2ddl.auto 29 Hibernate.initialize 43 hibernate.jdbc.use_streams_for_binary 144 hibernate.properties 143 hibernate3.jar 27 hibernate-annotations.jar 27 HibernateCallBack 290 hibernate-mapping 172 HibernateServiceBean 298 Hibernate-Typen 26 hints = {@QueryHint(...)} 350 HQL 32, 38, 103 HQL-, Criteria- und SQL-Vergleich 103 HSQLDialect 23 I Id 168 <composite-id> 185 <generator class=native> 179 <id> 182 <key-property> 185 @EmbeddedId 184 @IdClass 186 assigned 178, 181 Composite Id 183 equals 183 foreign 182 GenerationType.AUTO 179 GenerationType.IDENTITY 180 GenerationType.SEQUENCE 179 GenerationType.TABLE 180 generator class 182 GenericGenerator 180 guid 181 hashCode 183 hilo 181 Id 176 identity 181 increment 182 künstlicher Schlüssel 178 MultipleHiLoPerTableGenerator 182 native 181 Natürliche Schlüssel 177 select 181 seqhilo 181 sequence 181 Trigger 181 unsaved-value 182 uuid 182 implicit 175 in 113 indexes 322 Inheritance 257 InheritanceType.JOINED 267, 343 InheritanceType.SINGLE_TABLE 263, 343 InheritanceType.TABLE_PER_CLASS 275, 343 initialValue=1 325 inner join 106 insert=false 206 InstrumentTask 127 Interceptor 131 inverse 206 inverseJoinColumns 228, 336 Inversion of Control 308 J Java Persistence API 301 JavaServer Faces 295 Exception Handling 296 javax.persistence. 167 JBoss Application-Server 34 JBoss Cache 166 JBoss JPA 311 JBoss Treecache 166 jdbc.batch_size 148 jdbc.batch_versioned_data 148 jdbc.factory_class 148 jdbc.fetch_size 148 jdbc.use_get_generated_keys 149 365 635-0.book Seite 366 Montag, 6. August 2007 12:52 12 Index jdbc.use_scrollable_resultset 148 jdbc.use_streams_for_binary 149 JDBCTransactionFactory 90 JDBC-Transaktionen 88 Jgroups 165 JNDI 154 JNDI Connection Pool 154 jndi.class 145 jndi.eineEinstellung 145 jndi.url 145 Join 106 joinColumns 228, 336 JPA 301 JPA und EJB3 @TransactionAttribute 308 Entity-Manager 301 EntityManager 304 EntityManagerFactory 303–304 find 304 getReference 307 Glassfish 310 Inversion of Control 308 JBoss 311 JPA 301 JPA – Java-Persistenz-API 171 persist 304 persistence.xml 302 Persistenz-Provider 302 remove 304 Toplink 301 TransactionAttributeType 308 MANDATORY 309 NEVER 309 NOT_SUPPORTED 309 REQUIRED 309 REQUIRES_NEW 309 SUPPORTS 309 JTA – Java Transaction API 88 jta.UserTransaction 148 K Komponenten Mapping 243 Komposition 38, 244 Konfiguration Konfiguration mit Java 144 Konkurrierender Zugriff 100 Konstruktor 20, 58 Konversationen 94 366 L lazy 175 Lazy Initialization 40, 47 LazyCollectionOption.EXTRA 342 LazyCollectionOption.FALSE 342 LazyCollectionOption.TRUE 342 Lazy-Fetching 42 LazyInitializationException 41, 95 LazyToOneOption.FALSE 343 LazyToOneOption.NO_PROXY 343 LazyToOneOption.PROXY 343 left join fetch 43, 118 List 190 LoadEventListener 132 LOB (Large Object) 134 LocalSessionFactoryBean 287 LockMode.NONE 51 LockMode.READ 51 LockMode.UPGRADE 51, 103 Logging log4j 29 log4j.logger.org.hibernate.cache 116 log4j.logger.org.hibernate.id 116 log4j.logger.org.hibernate.SQL 116 log4j.logger.org.hibernate.type 116 Löschen von Daten 53 Lucene 134 M Map 190 mappedBy 206 Mapping Beziehungen 187 Komponenten 243 Vererbung 257 Vor- und Nachteile 190 Mapping von Beziehungen <array> 203 <bag> 195 <collection-id> 202 <idbag> 202 <join> 227 <list> 201 <many-to-many> 226, 229 <many-to-one> 211, 220 <map> 198 <map-key> 198 635-0.book Seite 367 Montag, 6. August 2007 12:52 12 Index <one-to-one> 214 <primitive-array> 204 <set> 193, 218 @AttributeOverrides 197 @CollectionOfElements 197, 203 @Column 197 @IndexColumn 201, 203 @JoinColumn 192, 194, 197 @JoinTable 197, 226, 228 @ManyToMany 228 @ManyToOne 220 @MapKey 198 @MapKeyManyToMany 234 @OneToMany 192, 194, 201, 218 @OneToOne 210 @PrimaryKeyJoinColumn 216 @Sort 194 1:1 Beziehung 210 1:n-Beziehung 216 Array 191 Bag 190, 195 bidirektional 205 Fehlerpotenzial manyToMany 230 Foreign Key Constraint 205 insert=false 206 inverse 206 inverseJoinColumns 228 joinColumns 228 List 191 m:n-Beziehung 227 Map 191, 196 mappedBy 206 property-ref 214 rekursive Beziehung 236 Set 190, 192 SortedMap 191 SortedSet 190, 193 strategy = foreign 216 unidirektional 204 update=false 206 Verwalten der Beziehung 206 Vor- und Nachteile 190 Mapping von Komponenten <component> 246 <composite-element> 248 <composite-id> 256 <parent> 246 @CollectionOfElements 247 @Embeddable 245 @Embedded 245 @Parent 245 einfache Komponente 244 Fehlerpotenzial bei non-indexed 249 Komponenten 243 Liste von Komponenten 247 Set von Komponenten 249 zusammengesetzte Primärschlüssel 254 Mapping von Vererbung <discriminator> 264 <join table=..> 272 <joined-subclass> 268 <many-to-any> 280 <meta-value> 280 <subclass> 264, 272 <union-subclass> 277 @DiscriminatorColumn 263 @Inheritance 263 @MappedSuperclass 282 Auswahl des Mapping-Ansatzes 259 Discriminator Column 38 discriminatorType 263 DiscriminatorType.STRING 263 InheritanceType.JOINED 267 InheritanceType.SINGLE_TABLE 263 InheritanceType.TABLE_PER_CLASS 275 Klassenhierarchie in einer Tabelle 262 Klassenhierarchie mit einer Tabelle für jede konkrete Klasse 274 Klassenhierarchie mit einer Tabelle pro Klasse 266 Klassenhierarchie mit einer Tabelle pro Klasse + Discriminator 271 Klassenhierarchie mit einer Tabelle pro Unterklasse 279, 281 Vererbung 257 MatchMode.START 110 max_fetch_depth 151 mutable 173 mutable = true 320 MyFaces 295 MySQL5Dialect 23 N name 173 Named Queries 128 node 176 NonUniqueObjectException 50, 95 367 635-0.book Seite 368 Montag, 6. August 2007 12:52 12 Index NotFoundAction.EXCEPTION 340 NotFoundAction.IGNORE 340 O Object-Relational-Mapping 13 oid 136 on_close 146 OnDeleteAction.CASCADE 340 OnDeleteAction.NO_ACTION 340 Open-Session-in-View 42, 96 optimisticLock 101, 175 OptimisticLockType.ALL 101, 321 OptimisticLockType.DIRTY 102, 321 OptimisticLockType.VERSION 321 Optimistisches Sperren optimisticLock = OptimisticLockType.VERSION 321 optimistisches Sperren 100 Optimistisches Sperren mit JSF 296 oracle.sql.CLOB 139 OracleDialect 23 order_updates 151 OS Cache 165 P package 173 Performance 115 persist 304 persistence.xml 302 persistent 44 Persistenz 14 Persistenz-Provider 302 persister 175 persister = customPersister 321 Pessimistisches Sperren 102 pkJoinColumns 323 pojo 150 polymorphism 175 polymorphism = PolymorphismType.IMPLICIT 321 Polymorphismus 257 Primärschlüssel 176 Projections 108 Projections.count 108 Projections.groupProperty 108 Projections.projectionList 108 Projections.property 108 368 Property.forName 113 PropertyAccessor 172, 183 property-ref 214 proxy 174 Q query = fromComputerBook), 350 Query Cache 163 query.factory_class 152 query.substitutions 152 R Read-Only-Mapping 120 referencedColumnName=id 335 remove 304 Restrictions.disjunction 111 Restrictions.eq 109 Restrictions.in 110 Restrictions.isEmpty 114 Restrictions.like 110 Restrictions.lt 111 Restrictions.or 111 ResultTransformer 43 Reverse Engineering 35 rollBack 89 rowid 176 S SaveOrUpdateEventListener 132 schema 172–173 Scrollable Resultsets 148 ScrollableResults 122 ScrollMode.FORWARD_ONLY 122 Second-Level-Cache 157 select-before-update 174 selectBeforeUpdate = false 320 SequenceGenerator 168 sequenceName=dbSequenceName, 325 Serializable 20, 58 SerializableClob 139 Servletfilter 96 Session 28 Extended Session 97 First Level Cache 87 FlushMode 97 Kurze Lebensdauer 95 635-0.book Seite 369 Montag, 6. August 2007 12:52 12 Index Lange Lebensdauer 97 Lange Lebensdauer mit JPA und EJB 3 99 Open-Session-in-View 96 Session schließen 34 session.beginTransaction 89 session.clear 124 session.close 89 session.createCriteria 106 session.createQuery 32, 105 session.createSQLQuery 115 session.delete 31, 53–54 session.delete bei Beziehungen 53 session.evict 55 session.flush 55, 88, 124 session.get 50, 53 session.getNamedQuery 128 session.getStatistics 122 session.load 307 session.lock 49–50 session.merge 49, 52 session.persist 307 session.refresh 55 session.save 31, 47 session.saveOrUpdate 52 session.update 31, 49, 51 Session-Lebensdauer 94 session.close 34 session_factory_name 147 SessionFactory Beispiel 28 factory.openSession 89 getCurrentSession 90 HibernateSessionFactory 91 HibernateUtil 91 JBossTransactionManagerLookup 91 Sessionkontext 90 Set 190 setFetchMode 118 setParameterList 110 setProjection 108 setReadOnly 108 setResultTransformer 43 show_sql 150 skalare Werte 105 some 113 SortedMap 190 SortedSet 190 Spring DataSourceTransactionManager 292 Exception Handling 291 HibernateCallBack 290 Konfiguration 285 Problembereich Transaktionssteuerung 287 Propagation.MANDATORY 292 Propagation.NESTED 292 Propagation.NEVER 292 Propagation.NOT_SUPPORTED 292 Propagation.REQUIRES_NEW 292 Propagation.SUPPORTS 292 Spring Templates 288 Transaktionssteuerung 291 Springframework @Transactional 291 AnnotationSessionFactoryBean 286 HibernateTransactionManager 291 LocalSessionFactoryBean 287 Propagation.REQUIRED 291 SQL 104 SQLServerDialect 23 StaleObjectStateException 100 StandardQueryCache 162–163 Stored Procedures 125 Struts Framework Exception Handling 294 optimistisches Sperren 293 Struts 293 subselect 176 Swarm Cache 165 SybaseDialect 23 T Tabellen erstellen 29 Table 168, 173 targetElement = Country.class 340 targetEntity = Invoice1.class, 331 TemporalType.DATE 329 TemporalType.NONE 329 TemporalType.TIME 329 TemporalType.TIMESTAMP 329 ternary 233 thread 90 Thread local 90 ThreadLocalSessionContext 90 Tomcat JNDI 154 Tools Eclipse 34 369 635-0.book Seite 370 Montag, 6. August 2007 12:52 12 Index Hibernate Tools 37 IntelliJ 37 MyEclipse 34 Netbeans 37 WebTool-Plugin 58 transaction.auto_close_session 91, 93, 147 transaction.factory_class 147 transaction.flush_before_completion 93, 147 transaction.manager_lookup_class 147 TransactionAttributeType 308 MANDATORY 309 NEVER 309 NOT_SUPPORTED 309 REQUIRED 309 REQUIRES_NEW 309 SUPPORTS 309 Transaktionssteuerung 76, 80 transient 44 Treecache 166 TreeMap 190 TreeSet 190 type 26 U Unidirektional 204 uniqueConstraints = { ...} 322 uniqueResult 112 UnsupportedOperationException 115 update=false 206 UpdateTimestampsCache 162 use_identifer_rollback 151 use_sql_comments 116, 151 V ValidateEventListener 133 Validator 133 Vererbung 38, 257 Versionsspalte 100 W where 175 370 X XML Mapping abstract 176 auto-import 172 batch-size 175 catalog 172, 174 check 176 class 173 default-access 172 default-cascade 172 default-lazy 172 discriminator-value 173 dynamic-insert 174 dynamic-update 174 entity-name 176 hibernate-mapping 172 lazy 175 mutable 173 name 173 node 176 optimistic-lock 175 package 173 persister 175 polymorphism 175 proxy 174 rowid 176 schema 172–173 select-before-update 174 subselect 176 table 173 where 175 XML Mapping 167