Objektrelationales Mapping (O/R-M oder ORM) bietet Programmierern eine objektorientierte Sicht auf Tabellen und Beziehungen in relationalen Datenbank-Management-Systemen (RDBMS). Statt mit SQL-Statements wird mit Objekten operiert.
Hibernate ist Open Source (LGPL) und einer der bekanntesten O/R Persistence Services für Java. Hibernate kann sowohl in Standalone-Java-Applicationen als auch in Plain Old Java Objects (POJOs) als auch in EJBs in Java-EE-Containern eingesetzt werden.
Der folgende Text ersetzt keine Doku zu Hibernate, aber demonstriert einige Features anhand eines einfachen lauffähigen Beispiels.
Verwendet wird hier Hibernate 3.2.5. Überlegen Sie, ob Sie statt des proprietären Hibernate-APIs den mittlerweile verfügbaren Standard JPA (Java Persistence API) verwenden sollten. Als JPA-Implementation können Sie trotzdem Hibernate verwenden (allerdings dann in einer aktuelleren Version).
Da einige Begriffe nicht immer einheitlich verwendet werden, folgen kurze Definitionen für den Gebrauch in diesem Dokument:
POJO: | Plain Old Java Objects: "Einfache" Java-Objekte (im Gegensatz zu aufwändigen EJBs für Java-EE-Container) (siehe http://www.martinfowler.com/bliki/POJO.html und http://www.hibernate.org/hib_docs/v3/reference/en/html/persistent-classes.html) |
JavaBean: |
Zu JavaBeans gibt es zwei unterschiedliche Definitionen: a) Java-Klassen mit 'private'-Attributen und Gettern/Settern (so sind JavaBeans in diesem Dokument gemeint) b) Das Gleiche, aber erweitert um Funktionalität für den Einsatz als wiederverwendbare konfigurierbare (GUI-)Komponente (siehe http://java.sun.com/products/javabeans) |
Enterprise JavaBean: | JavaBeans dürfen nicht mit Enterprise JavaBeans (EJB) verwechselt werden: Während JavaBeans einfache "POJOs" sein können, müssen Enterprise JavaBeans genauen Vorschriften entsprechen und sind nur in Java-EE-Containern lauffähig (siehe jee-ejb2.htm#JavaBean-versus-EnterpriseJavaBean) |
VO (Value Object): | Datencontainer: Enthält Datenattribute und oft hierfür Getter/Setter, aber enthält möglichst keine weiteren Methoden und insbesondere keine Businesslogik ("only State, no Behaviour") (siehe http://martinfowler.com/eaaCatalog/valueObject.html) |
TO (Transfer Object): | Wie VO, aber serialisierbar und insbesondere zum Transport von Daten über Layer- oder Tier-Grenzen geeignet (siehe sw-patterns.htm#BluePrints, http://www.corej2eepatterns.com/Patterns2ndEd/TransferObject.htm und http://java.sun.com/blueprints/corej2eepatterns/Patterns/TransferObject.html) |
DTO (Data Transfer Object): | TO nach Definition von Martin Fowler (siehe http://martinfowler.com/eaaCatalog/dataTransferObject.html) |
DAO (Data Access Object): | Kapselt Zugriffsmethoden ("CRUD" ...) des Persistenz-Layers, Datenübertragung per TO (siehe sw-patterns.htm#BluePrints, http://www.corej2eepatterns.com/Patterns2ndEd/DataAccessObject.htm und http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html) |
Entity Object: | VO mit Datenbankbezug: VO ist erweitert um ein Attribut für einen datenbanktechnischen Primary Key und enthält Attribute, die einer Zeile einer Datenbanktabelle entsprechen; Falls das Entity Object nicht "detached" ist, werden Manipulationen an Attributen persistiert (siehe auch http://de.wikipedia.org/wiki/Entit%C3%A4t_(Informatik)) |
Domain Object: |
Am Domain Model
orientierte Geschäftsobjekte ("Real-world Business Entities");
Wird in verschiedenen Bedeutungen verwendet: a) Synonym zu Entity-Objekt, also als Repräsentation der Daten einer einzelnen Datenbanktabellenzeile b) Repräsentation einer Datenbanktabelle inklusive "Behaviour"-Methoden c) Objekt in einem technologiefreien OOD-Model (Object Oriented Design) (siehe auch http://domaindrivendesign.org) |
Domain Object Model, Persistence Object Model, Transfer Object Model: |
Eigentlich müsste zwischen diesen drei Datenmodellen unterschieden werden, aber in der Praxis werden sie oft gleichgesetzt: a) Domain Object Model (orientiert an Geschäftsprozessen, technologiefreies OOA/OOD-Ergebnis, "Conceptual Model") b) Persistence Object Model (auch "Technical Domain Model" genannt: an Persistenztechnologie angepasstes Modell, optimiert/normalisiert) c) Transfer Object Model (TO-Datenmodell für Tier-Grenze z.B. zum Presentation-Client oder SOA Web Service) (siehe http://www.hybridlabs.org/data/TransferObjectModel-vs-PersistenceObjectModel.pdf) |
DAO/DTO- versus Domain-Object-Strategie
Grundsätzlich werden häufig folgende zwei Ansätze bzw. Strategien unterschieden, die verkürzt folgendermaßen beschrieben werden können:
DAO/DTO-Strategie:
Die Entkopplung von der Datenbank erfolgt im DAO-Layer. Die Daten werden über DTOs weitergeben. Dies entspricht am ehesten den Sun BluePrints J2EE Patterns und ist eine sinnvolle Strategie, wenn kein ORM-Tool (wie Hibernate) eingesetzt wird.
Domain-Object-Strategie:
Beim Einsatz eines ORM-Tools (wie Hibernate) macht ein zusätzlicher DAO-Layer keinen Sinn, da die üblichen CRUD-Operationen (Create/Read/Update/Delete) nicht explizit aufgerufen werden müssen (dies erledigt das ORM-Tool im Hintergrund: "transitive Persistenz"). Auch werden keine DTOs zum Datentransport benötigt. Stattdessen werden die (dem Hibernate-Mapping entsprechenden) Entity-Domain-Objekte verwendet. Die Domain-Objekte können als "Detached Object" auch losgelöst von der Hibernate-Session verwendet und weitergereicht werden (und nach der Bearbeitung in einer neuen Hibernate-Session mit 'update()' wieder persistiert werden). Die Weitergabe der Domain-Objekte kann bis in den Client-Tier erfolgen (bzw. z.B. bis in SOA Web Services), allerdings werden an Tier-Grenzen oft doch speziell angepasste DTOs bevorzugt.
Im folgenden Beispiel werden alle Dateien von Hand erzeugt. Lediglich die Erzeugung der Datenbanktabellen erfolgt automatisch. Folgende Tools bieten weitere Unterstützung:
Top-down | mit XDoclet: | Entity-Java-Klassen --> Hibernate Mapping XML |
mit hbm2ddl: | Hibernate Mapping XML --> Datenbank DDL | |
Bottom-up | mit Middlegen: | Datenbank DDL --> Hibernate Mapping XML |
mit hbm2java: | Hibernate Mapping XML --> Entity-Java-Klassen |
SessionFactory | Lädt die Konfiguration und die Mappings (üblicherweise aus XML-Dateien). Pro Datenbank wird eine eigene SessionFactory benötigt (unüblich aber erlaubt wären auch mehrere SessionFactories). Die SessionFactory wird normalerweise nur einmal in einer Anwendung erzeugt (siehe hierzu auch unten). SessionFactory ist thread-safe und kann für mehrere Threads verwendet werden. |
Session | Bindeglied zwischen der Java-Applikation und den Hibernate-Diensten. Bietet Methoden für Insert-, Update-, Delete- und Query-Operationen. Session-Objekte sollten mit möglichst kurzer Lebensdauer verwendet werden. Session ist nicht thread-safe und darf nur von einem Thread verwendet werden (siehe hierzu auch unten). |
Transaction | Bildet JDBC- und JTA-Transaktionen
ab (siehe unten). Geschachtelte Transaktionen werden nicht unterstützt. Innerhalb einer Session können nacheinander mehrere Transaktionen stattfinden. Parallele (konkurrierende) Transaktionen sind nur in getrennten Sessions möglich. |
Folgende Tabellenstruktur wird in der Datenbank implementiert:
|
← |
|
→ |
|
|||||||||||||||||||||||||||||||||||||||
|
Die entsprechenden Entity-Java-Klassen (POJO-JavaBeans) haben folgende Struktur:
|
→ ← |
|
||||||||||||||||||||||||||||||||||||||||
|
Die Datenbanktabelle 'Bestellungen' benötigt keine Entity-Java-Klasse, da Bestellungen über die 'Set'-Collections der anderen Entity-Java-Klassen bearbeitet werden.
Die im Folgenden vorgestellte Hibernate-Beispielanwendung ist ein einfaches Kommandozeilenprogramm, welches die Datenbank HSqlDb verwendet. Für andere Datenbanken müsste die Hibernate-SessionFactory-Konfigurationsdatei 'hibernate.cfg.xml' geändert werden.
Ihr Projektverzeichnis sieht jetzt ungefähr so aus:
[\MeinWorkspace] '- [HibernateTest] |- [bin] |- [hSqlDbData] |- [lib] | |- asm.jar, cglib-2.1.3.jar, checkstyle-all.jar, commons-collections-2.1.1.jar | '- dom4j-1.6.1.jar, hibernate3.jar, hsqldb.jar, jta.jar, log4j-1.2.11.jar '- [src] '- [mypackage]
log4j.rootCategory=INFO, A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%-5p - %m%n
Erklärungen zu log4j finden Sie unter java-log4j.htm und http://logging.apache.org/log4j/docs.
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class"> org.hsqldb.jdbcDriver </property> <property name="hibernate.connection.url"> jdbc:hsqldb:file:./hSqlDbData/myDB;shutdown=true </property> <property name="hibernate.connection.username">sa</property> <property name="hibernate.connection.password"></property> <property name="dialect">org.hibernate.dialect.HSQLDialect</property> <property name="show_sql">false</property> <property name="transaction.factory_class"> org.hibernate.transaction.JDBCTransactionFactory </property> <property name="hibernate.cache.provider_class"> org.hibernate.cache.HashtableCacheProvider </property> <property name="hibernate.hbm2ddl.auto">update</property> <mapping resource="Kunde.hbm.xml"/> <mapping resource="Speise.hbm.xml"/> <mapping resource="Kommentar.hbm.xml"/> </session-factory> </hibernate-configuration>
Den hSqlDb-Datenverzeichnispfad in der "hibernate.connection.url" können Sie an Ihre Wünsche anpassen.
Wenn Sie eine andere Datenbank verwenden wollen, müssen Sie als '...driver_class' und '...url' die unter java-sql.htm#JDBC für verschiedene Datenbanken genannten Strings einsetzen, als 'dialect' z.B. 'OracleDialect', 'Oracle9Dialect', 'DB2Dialect', 'DB2400Dialect', 'MySQLInnoDBDialect', 'MySQLMyISAMDialect' bzw. 'PostgreSQLDialect' einsetzen und je nach Datenbank eventuell noch weitere Parameter setzen.
In Java EE Application Servern wird die Datenbank statt über '...driver_class' und '...url' meistens über 'connection.datasource'-JNDI-Pfade angesprochen und die (fertig initialisierte) 'SessionFactory' wird über JNDI veröffentlicht, indem die Property 'session_factory_name' gesetzt wird. Dies wird weiter unten gezeigt.
Weiteres zu den Konfigurationsoptionen siehe http://www.hibernate.org/hib_docs/v3/reference/en/html/session-configuration.html.
Wichtig sind die unteren drei 'Mapping'-Verweise auf
'Kunde.hbm.xml', 'Speise.hbm.xml' und 'Kommentar.hbm.xml'.
Diese drei Dateien werden im Folgenden vorgestellt.
Zu beachten ist, dass die Verknüpfungstabelle 'Bestellungen' keinen gesonderten
'Mapping'-Eintrag erhält und trotzdem automatisch die entsprechende Datenbanktabelle kreiert wird.
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="mypackage.Kunde" table="KUNDEN"> <id name="id" column="ID" type="integer"> <generator class="native"/> </id> <property name="name" column="NAME" type="string" not-null="true"/> <property name="plz" column="PLZ" type="integer"/> <property name="ort" column="ORT" type="string"/> <property name="datum" column="DATUM" type="date"/> <set name="speisen" table="BESTELLUNGEN" lazy="true"> <key column="ID_KUNDE"/> <many-to-many class="mypackage.Speise" column="ID_SPEISE"/> </set> </class> </hibernate-mapping>
Die Kunden sollen mit den Speisen in Beziehung stehen. Die Beziehungen werden in der Datenbanktabelle 'BESTELLUNGEN' gespeichert. Die vom Kunden bestellten Speisen sind in der Entity-JavaBean-Klasse 'Kunde' über die 'Set'-Collection 'speisen' zugänglich.
Da ein Kunde mehrere Speisen bestellen kann und jede Speise von mehreren Kunden bestellt werden kann, muss die Klasse 'Speise' über eine 'many-to-many'-Relation verknüpft werden. 'ID_KUNDE' und 'ID_SPEISE' sind die Foreign Keys (Verknüpfungs-Fremdschlüssel) in der Tabelle 'BESTELLUNGEN'.
Weiteres zu den Beziehungskonfigurationsoptionen (z.B. Collections und 'many-to-many') siehe unten und http://www.hibernate.org/hib_docs/v3/reference/en/html/collections.html.
'lazy="true"' bedeutet, dass nicht sofort benötigte Daten erst on demand (bei Bedarf) aus der Datenbank geholt werden.
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="mypackage.Speise" table="SPEISEN"> <id name="id" column="ID" type="integer"> <generator class="native"/> </id> <property name="gericht" column="GERICHT" type="string" not-null="true"/> <property name="zutaten" column="ZUTATEN" type="string"/> <property name="preis" column="PREIS" type="double"/> <set name="kunden" table="BESTELLUNGEN" lazy="true"> <key column="ID_SPEISE"/> <many-to-many class="mypackage.Kunde" column="ID_KUNDE"/> </set> <set name="kommentare" cascade="all" lazy="true"> <key column="ID_SPEISE"/> <one-to-many class="mypackage.Kommentar"/> </set> </class> </hibernate-mapping>
Wie oben für 'Kunde.hbm.xml' erläutert, wird auch die Entity-JavaBean-Klasse 'Speise' über die 'Set'-Collection 'kunden' und der Datenbanktabelle 'BESTELLUNGEN' über eine 'many-to-many'-Relation mit der Entity-JavaBean-Klasse 'Kunde' verknüpft.
Zusätzlich sollen zu jeder Speise Kommentare abgegeben werden können. Da pro Speise mehrere Kommentare möglich sein sollen, aber jeder Kommentar sich nur auf genau eine Speise beziehen soll, wird über die 'Set'-Collection 'kommentare' eine 'one-to-many'-Relation zu der Entity-JavaBean-Klasse 'Kommentar' implementiert.
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="mypackage.Kommentar" table="KOMMENTARE"> <id name="id" column="ID" type="integer"> <generator class="native"/> </id> <property name="text" column="TEXT" type="string" not-null="true"/> <property name="datum" column="DATUM" type="date"/> <many-to-one name="speise" column="ID_SPEISE" class="mypackage.Speise"/> </class> </hibernate-mapping>
Hier muss für die Entity-JavaBean-Klasse 'Kommentar' das Gegenstück zu obiger 'one-to-many'-Relation implementiert werden, also eine 'many-to-one'-Relation zur Entity-JavaBean-Klasse 'Speise'.
package mypackage; import java.util.*; public class Kunde { private int id; private String name; private int plz; private String ort; private Date datum; private Set speisen = new HashSet(); public int getId() { return id; } public String getName() { return name; } public int getPlz() { return plz; } public String getOrt() { return ort; } public Date getDatum() { return datum; } public Set getSpeisen() { return speisen; } public void setId( int id ) { this.id = id; } public void setName( String name ) { this.name = name; } public void setPlz( int plz ) { this.plz = plz; } public void setOrt( String ort ) { this.ort = ort; } public void setDatum( Date datum ) { this.datum = datum; } public void setSpeisen( Set speisen ) { this.speisen = speisen; } }
package mypackage; import java.util.*; public class Speise { private int id; private String gericht; private String zutaten; private double preis; private Set kunden = new HashSet(); private Set kommentare = new HashSet(); public int getId() { return id; } public String getGericht() { return gericht; } public String getZutaten() { return zutaten; } public double getPreis() { return preis; } public Set getKunden() { return kunden; } public Set getKommentare() { return kommentare; } public void setId( int id ) { this.id = id; } public void setGericht( String gericht ) { this.gericht = gericht; } public void setZutaten( String zutaten ) { this.zutaten = zutaten; } public void setPreis( double preis ) { this.preis = preis; } public void setKunden( Set kunden ) { this.kunden = kunden; } public void setKommentare( Set kommentare ) { this.kommentare = kommentare; } }
package mypackage; import java.util.*; public class Kommentar { private int id; private Speise speise; private String text; private Date datum; public int getId() { return id; } public Speise getSpeise() { return speise; } public String getText() { return text; } public Date getDatum() { return datum; } public void setId( int id ) { this.id = id; } public void setSpeise( Speise speise ) { this.speise = speise; } public void setText( String text ) { this.text = text; } public void setDatum( Date datum ) { this.datum = datum; } }
package mypackage; import java.util.*; import org.hibernate.*; import org.hibernate.cfg.Configuration; public class Pizzeria { private SessionFactory sessionFactory; public Pizzeria() { try { System.out.println( "------------------------------------------------------" ); System.out.println( "Initializing Hibernate" ); sessionFactory = new Configuration().configure().buildSessionFactory(); System.out.println( "Finished Initializing Hibernate" ); System.out.println( "------------------------------------------------------" ); } catch( HibernateException ex ) { ex.printStackTrace(); System.exit( 5 ); } } public static void main( String[] args ) { String[][] jobListe = new String[][] { { "Kunde", "Manfred", "52146", "Wuerselen" }, { "Kunde", "Torsten", "52072", "Aachen" }, { "Kunde", "Roland", "52134", "Herzogenrath" }, { "Kunde", "Josef", "52070", "Aachen" }, { "Kunde", "Alexander", "52134", "Herzogenrath" }, { "Kunde", "Achim", "52078", "Aachen" }, { "Kunde", "Werner", "52066", "Aachen" }, { "Speise", "Pizza Diabolo", "Teufelsohren", "5.5" }, { "Speise", "Pizza Vulkano", "Teig, Kaese, Vesuvtomaten", "6" }, { "Speise", "Pizza Feuro", "Pepperoni", "6.5" }, { "Speise", "Lasagno", "Nudeln, Hackfleisch", "6" }, { "Speise", "Salat Eskimo", "Eiswuerfel", "4.5" }, { "Bestellung", "Manfred", "Pizza Diabolo" }, { "Bestellung", "Torsten", "Lasagno" }, { "Bestellung", "Torsten", "Salat Eskimo" }, { "Bestellung", "Roland", "Pizza Feuro" }, { "Bestellung", "Achim", "Pizza Feuro" }, { "Zutaten", "Salat Eskimo", "Eiszapfen, Schnee" }, { "Ort", "Aachen" } }; Pizzeria pizzeria = new Pizzeria(); for( int i=0; i<jobListe.length; i++ ) { execute( pizzeria, jobListe[i] ); } System.exit( 0 ); } private static void execute( Pizzeria pizzeria, String[] job ) { if( 3 < job.length && job[0].equalsIgnoreCase( "Kunde" ) ) { pizzeria.addCustomer( job[1], job[2], job[3] ); } else if( 3 < job.length && job[0].equalsIgnoreCase( "Speise" ) ) { pizzeria.addMeal( job[1], job[2], job[3] ); } else if( 2 < job.length && job[0].equalsIgnoreCase( "Bestellung" ) ) { pizzeria.addOrder( job[1], job[2] ); } else if( 2 < job.length && job[0].equalsIgnoreCase( "Zutaten" ) ) { pizzeria.exchangeIngredients( job[1], job[2] ); } else if( 1 < job.length && job[0].equalsIgnoreCase( "Ort" ) ) { pizzeria.showFromCity( job[1] ); } pizzeria.showCustomers(); pizzeria.showMeals(); pizzeria.showOrders(); System.out.println( "\n------------------------------------------------------" ); } private void addCustomer( String name, String plz, String ort ) { Session sess = null; Transaction trx = null; try { sess = sessionFactory.openSession(); trx = sess.beginTransaction(); Kunde kunde = new Kunde(); kunde.setDatum( new Date() ); kunde.setName( name ); kunde.setOrt( ort ); try { kunde.setPlz( Integer.parseInt( plz ) ); } catch( Exception ex ) { } sess.save( kunde ); trx.commit(); } catch( HibernateException ex ) { if( trx != null ) try { trx.rollback(); } catch( HibernateException exRb ) {} throw new RuntimeException( ex.getMessage() ); } finally { try { if( sess != null ) sess.close(); } catch( Exception exCl ) {} } } private void addMeal( String gericht, String zutaten, String preis ) { Session sess = null; Transaction trx = null; try { sess = sessionFactory.openSession(); trx = sess.beginTransaction(); Speise speise = new Speise(); speise.setGericht( gericht ); speise.setZutaten( zutaten ); try { speise.setPreis( Double.parseDouble( preis ) ); } catch( Exception ex ) { } sess.save( speise ); trx.commit(); } catch( HibernateException ex ) { if( trx != null ) try { trx.rollback(); } catch( HibernateException exRb ) {} throw new RuntimeException( ex.getMessage() ); } finally { try { if( sess != null ) sess.close(); } catch( Exception exCl ) {} } } private void addOrder( String name, String gericht ) { Session sess = null; Transaction trx = null; try { sess = sessionFactory.openSession(); trx = sess.beginTransaction(); Iterator itrKunde = sess.createQuery( "select kunde from Kunde as kunde where kunde.name = '" + name + "'" ).iterate(); Iterator itrSpeise = sess.createQuery( "select speise from Speise as speise where speise.gericht = '" + gericht + "'" ).iterate(); if( itrKunde.hasNext() && itrSpeise.hasNext() ) { Kunde kunde = (Kunde) itrKunde.next(); Speise speise = (Speise) itrSpeise.next(); kunde.getSpeisen().add( speise ); } else { System.out.println( "\nFehler: Kunde oder Speise unbekannt." ); } trx.commit(); } catch( HibernateException ex ) { if( trx != null ) try { trx.rollback(); } catch( HibernateException exRb ) {} throw new RuntimeException( ex.getMessage() ); } finally { try { if( sess != null ) sess.close(); } catch( Exception exCl ) {} } } private void exchangeIngredients( String gericht, String zutaten ) { Session sess = null; Transaction trx = null; try { sess = sessionFactory.openSession(); trx = sess.beginTransaction(); String hql = "select speise from Speise as speise" + " where speise.gericht = :gericht"; Query query = sess.createQuery( hql ); query.setString( "gericht", gericht ); Iterator itr = query.iterate(); if( itr.hasNext() ) { Speise speise = (Speise) itr.next(); speise.setZutaten( zutaten ); } trx.commit(); } catch( HibernateException ex ) { if( trx != null ) try { trx.rollback(); } catch( HibernateException exRb ) {} throw new RuntimeException( ex.getMessage() ); } finally { try { if( sess != null ) sess.close(); } catch( Exception exCl ) {} } } private void showFromCity( String ort ) { Session sess = null; Transaction trx = null; try { sess = sessionFactory.openSession(); trx = sess.beginTransaction(); System.out.println( "\nKunden aus " + ort + ":" ); String hql = "select kunde from Kunde as kunde where kunde.ort = :ort"; Query query = sess.createQuery( hql ); query.setString( "ort", ort ); Iterator itr = query.iterate(); while( itr.hasNext() ) { Kunde kunde = (Kunde) itr.next(); System.out.println( "Kunde: " + kunde.getName() + " aus " + kunde.getPlz() + " " + kunde.getOrt() ); } trx.commit(); } catch( HibernateException ex ) { if( trx != null ) try { trx.rollback(); } catch( HibernateException exRb ) {} throw new RuntimeException( ex.getMessage() ); } finally { try { if( sess != null ) sess.close(); } catch( Exception exCl ) {} } } private void showCustomers() { Session sess = null; Transaction trx = null; try { sess = sessionFactory.openSession(); trx = sess.beginTransaction(); System.out.println( "\nKunden:" ); List kunden = sess.createQuery( "from Kunde" ).list(); for( int i=0; i<kunden.size(); i++ ) { Kunde kunde = (Kunde)kunden.get( i ); System.out.println( "Kunde: " + kunde.getName() + " aus " + kunde.getPlz() + " " + kunde.getOrt() + " seit " + kunde.getDatum() ); System.out.print( " Bestellungen:" ); Iterator itr = kunde.getSpeisen().iterator(); while( itr.hasNext() ) System.out.print( " '" + ((Speise) itr.next()).getGericht() + "'" ); System.out.println( "" ); } trx.commit(); } catch( HibernateException ex ) { if( trx != null ) try { trx.rollback(); } catch( HibernateException exRb ) {} throw new RuntimeException( ex.getMessage() ); } finally { try { if( sess != null ) sess.close(); } catch( Exception exCl ) {} } } private void showMeals() { Session sess = null; Transaction trx = null; try { sess = sessionFactory.openSession(); trx = sess.beginTransaction(); System.out.println( "\nSpeisen:" ); List speisen = sess.createQuery( "from Speise" ).list(); for( int i=0; i<speisen.size(); i++ ) { Speise speise = (Speise)speisen.get( i ); System.out.println( "Speise: " + speise.getGericht() + " mit " + speise.getZutaten() + " fuer " + speise.getPreis() + " Euro" ); System.out.print( " Bestellungen:" ); Iterator itr = speise.getKunden().iterator(); while( itr.hasNext() ) System.out.print( " '" + ((Kunde) itr.next()).getName() + "'" ); System.out.println( "" ); } trx.commit(); } catch( HibernateException ex ) { if( trx != null ) try { trx.rollback(); } catch( HibernateException exRb ) {} throw new RuntimeException( ex.getMessage() ); } finally { try { if( sess != null ) sess.close(); } catch( Exception exCl ) {} } } private void showOrders() { Session sess = null; Transaction trx = null; try { sess = sessionFactory.openSession(); trx = sess.beginTransaction(); System.out.println( "\nBestellungen:" ); Iterator itr = sess.createQuery( "select kunde.name, elements(kunde.speisen) from Kunde kunde" ).iterate(); while( itr.hasNext() ) { Object[] tuple = (Object[]) itr.next(); System.out.println( tuple[0] + ": " + ((Speise)tuple[1]).getGericht() + " (" + ((Speise)tuple[1]).getPreis() + " Euro)" ); } trx.commit(); } catch( HibernateException ex ) { if( trx != null ) try { trx.rollback(); } catch( HibernateException exRb ) {} throw new RuntimeException( ex.getMessage() ); } finally { try { if( sess != null ) sess.close(); } catch( Exception exCl ) {} } } }
Beachtenswert ist in der Methode 'addOrder()' die Sourcecodezeile
'kunde.getSpeisen().add( speise );'.
Dieses Hinzufügen der 'speise' zur 'Set'-Collection 'speisen' im 'kunde'-Objekt
bewirkt eine persistente Speicherung der Bestellung in der Datenbanktabelle 'BESTELLUNGEN',
obwohl der Tabellenname nicht genannt wird, es keine korrespondierende Entity-JavaBean gibt
und auch kein 'save()'- oder 'update()'-Kommando erfolgt.
Und obwohl nur zur
'Set'-Collection 'speisen' im 'kunde'-Objekt
hinzugefügt wird, ist die Bestellung auch in der
'Set'-Collection 'kunden' in 'Speise'-Objekten
vorhanden, wenn diese neu aus der Datenbank geladen werden.
Interessant ist auch die letzte Methode 'showOrders()':
Zum einen zeigt sie, dass Attribute der korrespondierenden 'speisen'-Objekte abgefragt werden können,
obwohl in dem HQL-'select ...'-Kommando nur aus dem Objekt 'from Kunde' gelesen wird.
Zum anderen zeigt sie, wie mehrere 'select ...'-Objekte ('kunde.name' und 'kunde.speisen')
in 'Object'-Arrays ('Tuple' oder 'Tupel' genannt) gespeichert und bearbeitet werden.
<project name="hibernate-test" default="run"> <property name="myAppClass" value="mypackage.Pizzeria" /> <property name="mySrcDir" value="${basedir}/src" /> <property name="myDestDir" value="${basedir}/bin" /> <property name="myLibDir" value="${basedir}/lib" /> <target name="compile"> <javac srcdir="${mySrcDir}" destdir="${myDestDir}" debug="on"> <classpath> <fileset dir="${myLibDir}"> <include name="*.jar" /> </fileset> </classpath> </javac> </target> <target name="copy-resources"> <copy todir="${myDestDir}"> <fileset dir="${mySrcDir}"> <exclude name="**/*.java" /> </fileset> </copy> </target> <target name="run" depends="compile,copy-resources"> <sleep seconds="1" /> <java fork="true" classname="${myAppClass}"> <classpath> <fileset dir="${myLibDir}"> <include name="*.jar" /> </fileset> <pathelement path="${myDestDir}" /> </classpath> <arg line="${args}" /> </java> </target> </project>
Ihr Projektverzeichnis sieht jetzt ungefähr so aus:
[\MeinWorkspace] '- [HibernateTest] |- [bin] | '- ... |- [hSqlDbData] | '- ... |- [lib] | |- asm.jar, cglib-2.1.3.jar, checkstyle-all.jar, commons-collections-2.1.1.jar | '- dom4j-1.6.1.jar, hibernate3.jar, hsqldb.jar, jta.jar, log4j-1.2.11.jar |- [src] | |- [mypackage] | | |- Kommentar.java | | |- Kunde.java | | |- Pizzeria.java | | '- Speise.java | |- hibernate.cfg.xml | |- Kommentar.hbm.xml | |- Kunde.hbm.xml | |- log4j.properties | '- Speise.hbm.xml '- build.xml
Öffnen Sie ein Kommandozeilenfenster ('Start' | 'Alle Programme' | 'Zubehör' | 'Eingabeaufforderung') und geben Sie folgende Befehle ein:
cd \MeinWorkspace\HibernateTest
ant
Um die Funktionalität des Programms zu verstehen, können Sie die 'jobListe' vielleicht anfangs kürzen, indem Sie die meisten Jobs vorübergehend auskommentieren, oder sogar nur einzelne Jobs ausführen.
Falls Sie eine Datenbank verwendet haben, die persistent speichert, können Sie sich die erzeugten Datenbanktabellen (zum Beispiel mit "SQuirreL") mit folgenden SQL-Kommandos ansehen:
SELECT * FROM KUNDEN
SELECT * FROM SPEISEN
SELECT * FROM KOMMENTARE
SELECT * FROM BESTELLUNGEN
SELECT NAME, GERICHT, PREIS FROM BESTELLUNGEN, KUNDEN, SPEISEN WHERE ID_KUNDE = KUNDEN.ID AND ID_SPEISE = SPEISEN.ID ORDER BY NAME
(Auch das letzte mit 'SELECT ...' beginnende Kommando als ein einzelnes Kommando.)
Um zum Debuggen mehr Meldungen und die einzelnen SQL-Kommandos zu sehen, sind Modifikationen in der 'hibernate.cfg.xml' und in der 'log4j.properties' erforderlich.
In der 'hibernate.cfg.xml' müssen Sie 'show_sql' auf 'true' setzen und zusätzlich empfiehlt es sich, die 'batch_size' zu verkleinen. Suchen Sie die entsprechenden Einträge in Ihrer 'hibernate.cfg.xml'-Datei und ändern Sie die Werte entsprechend. Falls entsprechende Zeilen fehlen, ergänzen Sie folgende Zeilen:
... <property name="show_sql">true</property> <property name="hibernate.jdbc.batch_size">0</property> ...
Die 'log4j.properties' könnte zum Beispiel folgendermaßen erweitert werden:
log4j.rootCategory=DEBUG, A1 log4j.appender.A1=org.apache.log4j.ConsoleAppender log4j.appender.A1.layout=org.apache.log4j.PatternLayout log4j.appender.A1.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p (%F:%L) %x - %m%n log4j.logger.de.meinedomain.meinspeziellespackage=DEBUG log4j.logger.org.hibernate=WARN log4j.logger.org.hibernate.cache.EhCacheProvider=ERROR log4j.logger.org.hibernate.hql=TRACE log4j.logger.org.hibernate.SQL=TRACE
Falls Sie Hibernate im Java EE Application Server betreiben, muss die 'log4j.properties'
in den Classpath des Java EE Application Servers aufgenommen werden.
Sehen Sie sich auch die 'log4j.properties'-Beispieldatei im Hibernate-Paket an.
Erklärungen zu den Hibernate-Packages finden Sie unter
http://www.hibernate.org/hib_docs/v3/reference/en/html_single/#configuration-logging.
Erklärungen zu log4j finden Sie unter
java-log4j.htm und
http://logging.apache.org/log4j/docs.
Eine gute Hilfe insbesondere in Java EE Application Servern kann das Logging der übertragenen SQL-Kommandos zum Beispiel mit P6Spy sein.
session.createQuery() | Returniert 'Query'-Objekt, über das gelesen wird. Menge der zu lesenden Elemente kann gesteuert werden mit 'query.setFirstResult()' und 'query.setMaxResults()'. |
query.list() | Returniert 'List' mit allen passenden Datensätzen. |
query.iterate() | Returniert 'Iterator' und lädt Datensätze erst 'on demand'. |
query.scroll() | Returniert 'ScrollableResults'. |
query.uniqueResult() | Returniert beliebiges, aber einzelnes 'Object'. |
session.get() | Sofortige DB-Abfrage; Existiert gesuchter Datensatz nicht, wird 'null' returniert. |
session.load() | Eventuell on-demand; Existiert gesuchter Datensatz nicht, wird Exception ausgelöst. |
session.save() | Für neue Java-Objekte (SQL-'INSERT'), erzeugt generische Primary Keys ('id') bei neu erzeugten Java-Datenobjekten. | 'save()', 'update()' und 'saveOrUpdate()'
persistieren 'transient Instances', also assoziieren Java-Objekte mit der Hibernate-Session. Das bedeuted noch nicht unbedingt die sofortige Speicherung in der DB, dies kann später erfolgen. | |
session.update() | Für Änderungen an persistenten Objekten (SQL-'UPDATE'). | 'update()' und 'saveOrUpdate()' brauchen normalerweise nicht aufgerufen zu werden, weil Änderungen automatisch bemerkt und gespeichert werden; Methoden werden benötigt, wenn Java-Datenobjekte in einer ersten Hibernate-Session geladen und später in einer anderen Hibernate-Session gespeichert werden sollen. | |
session.saveOrUpdate() | Je nach 'id' wie 'save()' bzw. 'update()'. | ||
session.flush() | Forciert eine Synchronisierung zwischen Hibernate-Session und Datenbank, speichert also alle neuen und geänderten Objekte und führt Löschungen durch. 'flush()' braucht normalerweise nicht explizit aufgerufen zu werden, da dies in 'Transaction.commit()' erfolgt. |
Java- Collection |
Hib.- Mapping |
Hib.-Mapp.- Elemente |
Beschreibung |
---|---|---|---|
[Java-Array] | <array> | <key> <list-index> <element> |
Array mit definierter Reihenfolge. '<list-index>' definiert die Spalte für die laufende Nummer. |
List | <list> | <key> <list-index> <element> |
Liste mit definierter Reihenfolge. '<list-index>' definiert die Spalte für die laufende Nummer. |
List | <bag> | <key> <element> |
Die Reihenfolge der Java-Liste geht beim Speichern verloren. Mit '<bag order-by="SpalteXy asc">' kann optional beim Auslesen aus der Datenbank eine Reihenfolge definiert werden. |
Set | <set> | <key> <element> |
Anders als 'List' kann 'Set' keine Dubletten haben
(Voraussetzung hierfür: sinnvolle 'equals()'- und 'hashCode()'-Methoden). Die Reihenfolge ist undefiniert. |
SortedSet | <set> | <key> <element> |
Sortiertes 'Set' (Java-Implementierung: 'TreeSet'), benötigt 'sort'-Attribut: '<set sort="natural">': "Natürliche" Reihenfolge der Elemente (setzt implementiertes 'Comparable' voraus). '<set sort="MeineComparatorKlasse">': Sortierung über externe Klasse, die 'Comparator' implementiert. |
Map | <map> | <key> <map-key> <element> |
Schlüssel-/Werte-Paare, unsortiert. |
SortedMap | <map> | <key> <map-key> <element> |
Wie 'Map', aber sortiert (Java-Implementierung: 'TreeMap'), benötigt 'sort'-Attribut wie 'SortedSet'. |
Statt '<element>' kann auch '<composite-element>' verwendet werden, beziehungsweise statt '<map-key>' auch '<composite-map-key>'.
Für die Java-Collections müssen deren Interfaces verwendet werden, also zum Beispiel 'List' und nicht etwa 'ArrayList'.
Die Größe einer Collection können Sie ermitteln mit:
'( (Integer) session.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()'.
Weitere Information siehe:
Hibernate-Collections: | http://www.hibernate.org/hib_docs/v3/reference/en/html/collections.html |
Java-Collections: | java-collections.htm |
Da das Erzeugen der 'SessionFactory' Zeit kostet, sollte dies nur einmal erfolgen. Dazu muss das erzeugte 'SessionFactory'-Objekt anwendungsweit verfügbar hinterlegt werden (ähnlich einem 'Singleton'). Je nach Anwendungsstruktur kommen hierfür verschiedene Wege in Frage.
Application Server
In Java EE Application Servern bietet sich die Speicherung des 'SessionFactory'-Objekts im JNDI an. Dies wird in der 'hibernate.cfg.xml'-Konfigurationsdatei über die Property 'session_factory_name' eingestellt (hier in einem Beispiel für JBoss und hSqlDb demonstriert):
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="session_factory_name"> hibernate/MeineSessionFactory </property> <property name="transaction.factory_class"> org.hibernate.transaction.CMTTransactionFactory </property> <property name="transaction.manager_lookup_class"> org.hibernate.transaction.JBossTransactionManagerLookup </property> <property name="connection.datasource"> java:MeinDatasourceName </property> <property name="dialect"> org.hibernate.dialect.HSQLDialect </property> <mapping resource="....hbm.xml" /> </session-factory> </hibernate-configuration>
Das Auslesen der 'SessionFactory' aus dem JNDI und das Erhalten der 'Session' funktioniert im Application Server folgendermaßen (bitte Code um Fehlerbehandlung ergänzen):
public class HibernateUtil { static { // Speichert 'SessionFactory'-Objekt einmalig im JNDI (weil 'session_factory_name' gesetzt ist): SessionFactory sessionFactory = (new Configuration()).configure().buildSessionFactory(); } public static SessionFactory getSessionFactory() { // Findet 'SessionFactory'-Objekt im JNDI: Context context = new InitialContext(); SessionFactory sessionFactory = (SessionFactory) context.lookup( "hibernate/MeineSessionFactory" ); return sessionFactory; } public static Session getCurrentSession() { // Dies funktioniert so nur im Java EE Application Server mit 'CMTTransactionFactory': Session session = getSessionFactory().getCurrentSession(); return session; } }
Webanwendung
Für 'Servlet'-basierende Webanwendungen (JSP, JSF, Struts, Spring, ...) bietet sich der 'ServletContext' zur Speicherung des 'SessionFactory'-Objekts an:
public class HibernateListener implements ServletContextListener { public void contextInitialized( ServletContextEvent servletContextEvent ) { SessionFactory sessionFactory = (new Configuration()).configure().buildSessionFactory(); servletContextEvent.getServletContext().setAttribute( "sessionFactory", sessionFactory ); } public void contextDestroyed( ServletContextEvent servletContextEvent ) { ((SessionFactory) servletContextEvent.getServletContext().getAttribute( "sessionFactory" )).close(); } }
Der erstellte 'ContextListener' muss in die 'web.xml' eingetragen werden:
<listener> <listener-class>myorg.mypackage.HibernateListener</listener-class> </listener>
Session über ThreadLocal
Die 'Session' ist nicht thread-safe und darf nur von einem Thread verwendet werden. Um dies sicherzustellen, kann es sinnvoll sein, das 'Session'-Objekt in einer ThreadLocal-Variabel zu speichern:
private static ThreadLocal sessionHolder = new ThreadLocal(); public Session getSession() { Session session = sessionHolder.get(); if( session == null || !session.isConnected() ) { session = getSessionFactory().openSession(); sessionHolder.set( session ); } return session; }
Ein ausführlicheres Beispiel hierzu finden Sie unter: http://www.hibernate.org/207.html.
Bitte lesen Sie vorab die Erläuterungen zu Transaktionen unter: jee-transaktionen.htm.
Manuelle JDBC-Transaktionen
Session session = null; try { session = sessionFactory.openSession(); // ... DB bearbeiten ... // ... session.flush(); session.connection().commit(); } catch( HibernateException ex ) { session.connection().rollback(); throw new RuntimeException( ex.getMessage() ); } finally { try { if( session != null ) session.close(); } catch( Exception exCl ) {} }
Hibernate-gesteuerte Transaktionen ('JDBCTransaction')
Session session = null; Transaction trx = null; try { session = sessionFactory.openSession(); trx = session.beginTransaction(); // ... DB bearbeiten ... // ... trx.commit(); } catch( HibernateException ex ) { if( trx != null ) try { trx.rollback(); } catch( HibernateException exRb ) {} throw new RuntimeException( ex.getMessage() ); } finally { try { if( session != null ) session.close(); } catch( Exception exCl ) {} }
JTA-gesteuerte Transaktionen ('JTATransaction') (z.B. in EJB in 'managed' Umgebung im Application Server)
SessionContext ctx = sessionContext; // (... gekürzt) Session session = null; try { session = sessionFactory.openSession(); // ... DB bearbeiten ... // ... session.flush(); } catch( HibernateException ex ) { ctx.getUserTransaction().setRollbackOnly(); // (... je nach EJB) throw new EJBException( ex ); } finally { try { if( session != null ) session.close(); } catch( Exception exCl ) {} }
In vielen Anwendungen werden Daten gelesen, eine Zeit lang vom Anwender (z.B. in einem GUI) bearbeitet und anschließend gespeichert. Damit die Anwendung von mehreren Benutzern parallel verwendet werden kann, ist eine Strategie eforderlich, die verhindert, dass die Daten gegenseitig überschrieben werden.
In diesem Zusammenhang wird der Begriff 'Long Transaction' verwendet, leider in zwei unterschiedlichen Bedeutungen:
Mit 'Long Transaction' kann eine einzelne zeitlich lange Transaktion gemeint sein, bei der das Lesen, Bearbeiten und Speichern der Daten innerhalb einer einzigen technischen Transaktion stattfindet. Bei dieser Art von 'Long Transaction' können die Daten leicht durch Sperren vor anderen konkurrierenden Zugriffen geschützt werden, zum Beispiel so wie unten für datenbanktechnisch realisiertes 'pessimistic Locking' gezeigt. Bei zu lange andauernden Transaktionen kann diese Vorgehensweise zu 'Transaction Time-out', hohem Verwaltungsaufwand in der Datenbank, Performanceverlust, Deadlocks und anderen Komplikationen führen.
Der Begriff 'Long Transaction' wird andererseits auch verwendet, wenn zwei getrennte Datenbanktransaktionen als eine einzige 'logische Transaktion' erscheinen sollen (auch 'Application Transaction' genannt). In der ersten Datenbanktransaktion werden die Daten gelesen, dann werden sie vom Anwender bearbeitet und anschließend in einer zweiten Datenbanktransaktion werden die geänderten Daten in die Datenbank gespeichert. Falls zwischenzeitlich ein anderer Anwender die selben Daten bearbeitet hat, sollte, um Inkonsistenzen zu vermeiden, der Speicherversuch des ersten Anwenders zu einer Fehlermeldung führen. Hierzu wird üblicherweise 'optimistic Locking' verwendet (siehe unten).
Oft wird eine Kombination aus 'pessimistic Locking' und 'optimistic Locking' gewählt: Datenbanktechnisch wird die Performance und Datenkonsistenz per 'optimistic Locking' sichergestellt, aber zusätzlich wird über die Anwendungslogik beim Versuch, Daten zu bearbeiten, die bereits eine andere Anwendung im Zugriff hat, entweder dieser Versuch abgelehnt oder die Daten werden 'read-only' nur angezeigt, aber können nicht geändert werden. Die Einbeziehung der Anwendungslogik hat bei innerbetrieblichen Mehrbenutzeranwendungen auch den Vorteil, dass angezeigt werden kann, welcher Mitarbeiter die Daten seit wann im Zugriff hat.
Beim 'pessimistic Locking' wird der Datensatz während eines Prozesses für andere Zugriffe gesperrt.
Anders als 'optimistic Locking' kann sich datenbanktechnisch realisiertes 'pessimistic Locking' nicht über zwei getrennte Datenbank-Transaktionen erstrecken, sondern gilt nur innerhalb einer Transaktion (siehe oben 'Long Transaction').
In Hibernate wird 'pessimistic Locking' angefordert über die Methoden 'Session.load(Class,id,LockMode)', 'Session.lock(Object,LockMode)' und 'Query.setLockMode(alias,LockMode)'.
'LockMode' kann die Werte 'NONE', 'READ', 'WRITE', 'UPGRADE' und 'UPGRADE_NOWAIT' annehmen.
Datenbanktechnisch werden Sperren über Varianten von "SELECT FOR UPDATE" realisiert.
Außer durch datenbanktechnische Realisierung kann 'pessimistic Locking' auch in der Anwendungslogik realisiert werden. Dann kann auch eine Sperre über aufgesplittete Transaktionen programmiert werden.
Beim 'optimistic Locking' wird der Datensatz während der Bearbeitung zwischen zwei Transaktionen nicht für andere Zugriffe blockiert, aber vor dem späteren Schreibversuch wird überprüft, ob der Datensatz noch aktuell ist (siehe oben 'Long Transaction'). Diese Überprüfung erfolgt normalerweise nicht durch Vergleich aller einzelnen Datenelemente, sondern über eine spezielle dafür vorgesehene 'version'- oder 'timestamp'-Spalte, die in den Hibernate-Mapping-XML-Dateien '...hbm.xml' deklariert werden muss und dann von Hibernate automatisch aktualisiert wird.
Hibernate unterstützt drei Vorgehensweisen für 'optimistic Locking':
'Session'-Instanz bleibt bestehen und wird über 'disconnect()' getrennt und über 'reconnect()' wieder verbunden:
Session session = sessionFactory.openSession(); Transaction tx = null; // ... // Erste Transaktion (Lesen): try { tx = session.beginTransaction(); myDataObj = session.get( ... ); tx.commit(); } catch( Exception ex ) { if( tx != null ) tx.rollback(); session.close(); throw ex; } session.disconnect(); // ... // Benutzer-Interaktionen ... // ... // Zweite Transaktion (Schreiben): session.reconnect(); try { tx = session.beginTransaction(); session.lock( myDataObj, LockMode.READ ); // myDataObj noch aktuell? myDataObj.setProperty( ... ); tx.commit(); } catch( Exception ex ) { if( tx != null ) tx.rollback(); throw ex; } finally { session.close(); }
Neue 'Session'-Instanz und 'update()' oder 'saveOrUpdate()' mit altem Datenobjekt:
// ... // 'myDataObj' ist Datenobjekt aus vorheriger Session myDataObj.setProperty( ... ); // ... // Neue Session: session = sessionFactory.openSession(); session.saveOrUpdate( myDataObj ); session.flush(); session.connection().commit(); session.close();
Neue 'Session'-Instanz, neues Datenobjekt und manueller Versionsvergleich:
// ... // 'myDataObj' ist Datenobjekt aus vorheriger Session // ... // Neue Session: session = sessionFactory.openSession(); int previousVersion = myDataObj.getVersion(); session.load( myDataObj, myDataObj.getKey() ); if( previousVersion != myDataObj.getVersion ) throw new StaleObjectStateException(); myDataObj.setProperty( ... ); session.flush(); session.connection().commit(); session.close();
Weiteres zu Transaktionen finden Sie unter:
jee-transaktionen.htm
Hibernate: Transaction strategy configuration
Hibernate: 'version' / 'timestamp'
Hibernate: Optimistic concurrency control
Hibernate: Pessimistic Locking
J2EE Transaction Frameworks, Baksi
Optimistic Locking pattern for EJBs, Transactionally safe EJB code, Akbar-Husain/Lane
Innerhalb der Hibernate-'Session' werden Daten über den integrierten 'First-Level-Cache' zwischengespeichert. Bei Lesezugriffen auf Datenbankobjekte wird zuerst überprüft, ob sich die Daten bereits im Session-Cache befinden. Nur wenn dies nicht der Fall ist, wird aus der Datenbank gelesen. Ebenso werden Schreibzugriffe vorerst nur im Cache ausgeführt. Erst ein 'flush' oder 'commit' schreibt die Daten tatsächlich in die Datenbank.
Vorteile:
Nachteile und Gefahren:
Im Zweifelsfall kann das Datenbankobjekt mit 'session.evict()' im Cache gelöscht werden und anschließend neu aus der Datenbank gelesen werden (oder alternativ mit 'session.clear()' der gesamte Cache geleert werden).
Einige der genannten Gefahren und Nachteile können vermieden oder zumindest abgeschwächt werden, wenn nach jedem Transaktionsende sofort die Hibernate-'Session' geschlossen wird.