Objektrelationales Mapping (O/R-M oder ORM) bietet Java-Programmierern eine objektorientierte Sicht auf Tabellen und Beziehungen in relationalen Datenbank-Management-Systemen (RDBMS). Statt mit SQL-Statements wird mit Java-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.
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/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 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;
Wird in verschiedenen Bedeutungen verwendet: a) Synonym zu Entity-Objekt, also als Repräsentation der Daten einer einzelnen Datenbanktabellenzeile b) Repräsentation einer kompletten 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 OOD-Ergebnis) 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) |
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-JavaBean --> 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-JavaBean |
Folgende Tabellenstruktur wird in der Datenbank implementiert:
|
← |
|
→ |
|
|||||||||||||||||||||||||||||||||||||||
|
Die entsprechenden Entity-JavaBean-Datenklassen haben folgende Struktur:
|
→ ← |
|
||||||||||||||||||||||||||||||||||||||||
|
Die Datenbanktabelle 'Bestellungen' benötigt keine Entity-JavaBean, da Bestellungen über die 'Set'-Collections der anderen Entity-JavaBeans 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.
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 2.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class"> org.hsqldb.jdbcDriver </property> <property name="hibernate.connection.url"> jdbc:hsqldb:file:D:/MeinWorkspace/HibernateTest/hSqlDbData/myDB </property> <property name="hibernate.connection.username">sa</property> <property name="hibernate.connection.password"></property> <property name="dialect">net.sf.hibernate.dialect.HSQLDialect</property> <property name="show_sql">false</property> <property name="transaction.factory_class"> net.sf.hibernate.transaction.JDBCTransactionFactory </property> <property name="hibernate.cache.provider_class"> net.sf.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 Pfad 'D:/MeinWorkspace/HibernateTest/hSqlDbData/myDB' in der "hibernate.connection.url" müssen Sie an Ihren hSqlDb-Datenverzeichnispfad 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. 'DB2Dialect', 'OracleDialect', 'Oracle9Dialect', 'MySQLDialect' bzw. 'PostgreSQLDialect' einsetzen und je nach Datenbank eventuell noch weitere Parameter setzen.
In Application Servern wird die Datenbank statt über '...driver_class' und '...url' meistens über 'connection.datasource'-JNDI-Pfade angesprochen.
Weiteres zu den Konfigurationsoptionen siehe http://www.hibernate.org/hib_docs/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 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.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 http://www.hibernate.org/hib_docs/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 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.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 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.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="idSpeise" 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 int idSpeise; private String text; private Date datum; public int getId() { return id; } public int getIdSpeise() { return idSpeise; } public String getText() { return text; } public Date getDatum() { return datum; } public void setId( int id ) { this.id = id; } public void setIdSpeise( int idSpeise ) { this.idSpeise = idSpeise; } public void setText( String text ) { this.text = text; } public void setDatum( Date datum ) { this.datum = datum; } }
package mypackage; import java.util.*; import net.sf.hibernate.*; import net.sf.hibernate.cfg.Configuration; public class Pizzeria { private SessionFactory sessionFactory; public Pizzeria() { try { System.out.println( "Initializing Hibernate" ); sessionFactory = new Configuration().configure().buildSessionFactory(); System.out.println( "Finished Initializing Hibernate" ); } catch( HibernateException ex ) { ex.printStackTrace(); System.exit( 5 ); } } public static void main( String[] args ) { Pizzeria instance = new Pizzeria(); if( 3 < args.length && args[0].equalsIgnoreCase( "Kunde" ) ) { instance.addCustomer( args[1], args[2], args[3] ); } else if( 3 < args.length && args[0].equalsIgnoreCase( "Speise" ) ) { instance.addMeal( args[1], args[2], args[3] ); } else if( 2 < args.length && args[0].equalsIgnoreCase( "Bestellung" ) ) { instance.addOrder( args[1], args[2] ); } else if( 2 < args.length && args[0].equalsIgnoreCase( "Zutaten" ) ) { instance.exchangeIngredients( args[1], args[2] ); } else if( 1 < args.length && args[0].equalsIgnoreCase( "Ort" ) ) { instance.showFromCity( args[1] ); } instance.showCustomers(); instance.showMeals(); instance.showOrders(); System.exit( 0 ); } 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.iterate( "select kunde from Kunde as kunde where kunde.name = '" + name + "'" ); Iterator itrSpeise = sess.iterate( "select speise from Speise as speise where speise.gericht = '" + gericht + "'" ); 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.find( "from Kunde" ); 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.find( "from Speise" ); 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.iterate( "select kunde.name, elements(kunde.speisen) from Kunde kunde" ); 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 (Tuples) 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>
Öffnen Sie ein Kommandozeilenfenster ('Start' | 'Alle Programme' | 'Zubehör' | 'Eingabeaufforderung')
und geben Sie folgende Befehle ein
(unter Windows XP können Sie den Kommandotext im Webbrowser mit gedrückter Maustaste markieren,
mit 'Strg+C' zwischenspeichern und irgendwo im Kommandozeilenfenster mit rechter Maustaste,
'Einfügen' und 'Return' ausführen).
Lästig ist in diesem einfachen Beispiel, dass für jedes Kommando alles komplett neu initialisiert wird,
was aber in richtigen Anwendungen natürlich nur einmal erfolgt.
Außerdem muss bei diesem Beispiel zwischen schreibenden Programmaufrufen eine kurze Pause eingelegt werden bis die vorherige Schreiboperation fertig gespeichert ist.
cd \MeinWorkspace\HibernateTest
ant
ant -Dargs="Kunde Manfred 52146 Wuerselen"
ant -Dargs="Kunde Torsten 52072 Aachen"
ant -Dargs="Kunde Roland 52134 Herzogenrath"
ant -Dargs="Kunde Josef 52070 Aachen"
ant -Dargs="Kunde Alexander 52134 Herzogenrath"
ant -Dargs="Kunde Achim 52078 Aachen"
ant -Dargs="Kunde Werner 52066 Aachen"
ant -Dargs="Speise 'Pizza Diabolo' 'Teufelsohren' 5.5"
ant -Dargs="Speise 'Pizza Vulkano' 'Teig, Kaese, Vesuvtomaten' 6"
ant -Dargs="Speise 'Pizza Feuro' 'Pepperoni' 6.5"
ant -Dargs="Speise 'Lasagno' 'Nudeln, Hackfleisch' 6"
ant -Dargs="Speise 'Salat Eskimo' 'Eiswuerfel' 4.5"
ant -Dargs="Bestellung Manfred 'Pizza Diabolo'"
ant -Dargs="Bestellung Torsten 'Lasagno'"
ant -Dargs="Bestellung Torsten 'Salat Eskimo'"
ant -Dargs="Bestellung Roland 'Pizza Feuro'"
ant -Dargs="Bestellung Achim 'Pizza Feuro'"
ant -Dargs="Zutaten 'Salat Eskimo' 'Eiszapfen, Schnee'"
ant -Dargs="Ort Aachen"
Sehen Sie sich die erzeugten Datenbanktabellen im HSQL Database Manager an.
Legen Sie vorher eine Kopie der Daten an, da der HSQL Database Manager die Daten
eventuell so verändert, dass die Anwendung sie nicht mehr verarbeiten kann.
Rufen Sie im Kommandozeilenfenster auf:
cd \MeinWorkspace\HibernateTest
md hSqlDbDataCopy
copy hSqlDbData\*.* hSqlDbDataCopy
java -classpath D:\MeinWorkspace\HibernateTest\lib\hsqldb.jar org.hsqldb.util.DatabaseManager -url "jdbc:hsqldb:file:D:/MeinWorkspace/HibernateTest/hSqlDbDataCopy/myDB"
(Auch das 'java ...'-Kommando bitte als ein einzelnes Kommando)
Klicken Sie im HSQL Database Manager im linken Fenster auf die [+]-Zeichen und sehen Sie sich die erzeugte Datenbankstruktur an.
Fügen Sie im rechten Eingabefenster nacheinander folgende SQL-Abfragen ein und betätigen Sie jeweils den 'Execute'-Button:
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 zu sehen, können Sie:
in der 'hibernate.cfg.xml'-Datei
'<property name="show_sql">false</property>' durch
'<property name="show_sql">true</property>' ersetzen,
in der 'log4j.properties'-Datei
'INFO' durch 'DEBUG' ersetzen oder
in der 'log4j.properties'-Datei
andere Einstellungen probieren, wie sie zum Beispiel in der 'log4j.properties' im Hibernate-Paket
(z.B. hibernate-2.1.8.zip) vorgeschlagen sind.
Bitte lesen Sie vorab die Erläuterungen zu Transaktionen unter: jee-transaktionen.htm.
Manuelle JDBC-Transaktionen
Session sess = null; try { sess = sessionFactory.openSession(); // ... DB bearbeiten ... // ... sess.flush(); sess.connection().commit(); } catch( HibernateException ex ) { sess.connection().rollback(); throw new RuntimeException( ex.getMessage() ); } finally { try { if( sess != null ) sess.close(); } catch( Exception exCl ) {} }
Hibernate-gesteuerte Transaktionen ('JDBCTransaction')
Session sess = null; Transaction trx = null; try { sess = sessionFactory.openSession(); trx = sess.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( sess != null ) sess.close(); } catch( Exception exCl ) {} }
JTA-gesteuerte Transaktionen ('JTATransaction') (z.B. in EJB in 'managed' Umgebung im Application Server)
SessionContext ctx = sessionContext; // (... gekürzt) Session sess = null; try { sess = sessionFactory.openSession(); // ... DB bearbeiten ... // ... sess.flush(); } catch( HibernateException ex ) { ctx.getUserTransaction().setRollbackOnly(); // (... je nach EJB) throw new EJBException( ex ); } finally { try { if( sess != null ) sess.close(); } catch( Exception exCl ) {} }
Viele Anwendungen erfordern aufgesplittete Transaktionen, die als eine einzige 'logische' Transaktion erscheinen sollen (so genannte 'long Transaction' oder 'Application Transaction'), aber aus mehreren Datenbank-Transaktionen bestehen. Beispielsweise werden Objekte aus der Datenbank gelesen, über ein Benutzer-Interface bearbeitet und später geändert wieder in die Datenbank gespeichert. Um Transaction Time-outs und andere Komplikationen zu vermeiden, werden solche zeitlich auseinanderliegenden Vorgänge auf zwei Transaktionen aufgesplittet.
Es wird unterschieden zwischen 'optimistic Locking' und 'pessimistic Locking'.
Beim 'optimistic Locking' wird der Datensatz während der 'long Transaction' nicht für andere Zugriffe blockiert, aber vor dem späteren Schreibversuch wird überprüft, ob der Datensatz noch aktuell ist. 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' in 'long Transactions':
'Session'-Instanz bleibt bestehen, 'disconnect()' und 'reconnect()'
Session sess = sessionFactory.openSession(); Transaction tx = null; // ... // Erste Transaktion (Lesen): try { tx = sess.beginTransaction(); myDataObj = sess.get( ... ); tx.commit(); } catch( Exception ex ) { if( tx != null ) tx.rollback(); sess.close(); throw ex; } sess.disconnect(); // ... // Benutzer-Interaktionen ... // ... // Zweite Transaktion (Schreiben): sess.reconnect(); try { tx = sess.beginTransaction(); sess.lock( myDataObj, LockMode.READ ); // myDataObj noch aktuell? myDataObj.setProperty( ... ); tx.commit(); } catch( Exception ex ) { if( tx != null ) tx.rollback(); throw ex; } finally { sess.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();
Beim 'pessimistic Locking' wird der Datensatz während der 'long Transaction' für andere Zugriffe blockiert.
'Pessimistic Locking' kann angefordert werden ü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.
Weiteres zu Transaktionen finden Sie unter:
jee-transaktionen.htm
Hibernate: Transaction strategy configuration
Hibernate: 'version' / 'timestamp'
Hibernate: Updating detached objects
Hibernate: Ending a Session, Committing the database transaction, Exception handling
Hibernate: Optimistic concurrency control
Hibernate: Pessimistic Locking
J2EE Transaction Frameworks, Baksi
Optimistic Locking pattern for EJBs, Transactionally safe EJB code, Akbar-Husain/Lane