Objektrelationales Mapping (O/R-M) mit Hibernate 2

+ andere TechDocs
+ SQL mit Java
+ SQL
+ hibernate.org
+


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.



Inhalt

  1. Vorbemerkungen zu Hibernate
  2. Hibernate-Beispielanwendung
    1. Datenbankstruktur, Struktur der Entity-JavaBeans
    2. Installation
    3. SessionFactory-Konfigurationsdatei hibernate.cfg.xml
    4. Hibernate-Mapping-XML-Dateien ...hbm.xml
    5. Entity-JavaBeans, Hauptprogramm
    6. build.xml
    7. HSQL Database Manager
  3. Transaktionen
  4. Weitere Bemerkungen zu Hibernate
  5. Links auf weiterführende Informationen


Vorbemerkungen zu Hibernate



Hibernate-Beispielanwendung

Folgende Tabellenstruktur wird in der Datenbank implementiert:

KUNDEN
ID INTEGER
NAME VARCHAR
PLZ INTEGER
ORT VARCHAR
DATUM DATE
  
BESTELLUNGEN
ID_KUNDE INTEGER
ID_SPEISE INTEGER
 
SPEISEN
ID INTEGER
GERICHT VARCHAR
ZUTATEN VARCHAR
PREIS NUMERIC
 
KOMMENTARE
ID INTEGER
ID_SPEISE INTEGER
TEXT VARCHAR
DATUM DATE

Die entsprechenden Entity-JavaBean-Datenklassen haben folgende Struktur:

Kunde
id int
name String
plz int
ort String
datum Date
speisen Set
 → 
 ← 
Speise
id int
gericht String
zutaten String
preis double
kunden Set
kommentare Set
↓ ↑
 
Kommentar
id int
idSpeise int
text String
datum Date

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.

  1. Legen Sie ein Projektverzeichnis an, zum Beispiel: 'D:\MeinWorkspace\HibernateTest'.
    Erzeugen Sie folgende Unterverzeichnisse:
    <project-root>/bin
    <project-root>/hSqlDbData
    <project-root>/lib
    <project-root>/src
    <project-root>/src/mypackage
  2. Downloaden Sie das hSqlDb-Paket (z.B. hsqldb_1_8_0_7.zip) von http://hsqldb.org. Extrahieren Sie daraus die Datei 'hsqldb.jar' und kopieren Sie sie nach '<project-root>/lib'.
    'hsqldb.jar' ist gleichzeitig JDBC-Treiber und Datenbank (siehe dazu java-sql.htm#hSqlDb).
  3. Downloaden Sie das Hibernate2-Paket (z.B. hibernate-2.1.8.zip) von http://www.hibernate.org. Extrahieren Sie daraus die Datei 'hibernate2.jar' und aus dem 'lib'-Verzeichnis die Dateien 'asm-util-1.5.3.jar', 'cglib-full.jar', 'commons-collections.jar', 'commons-logging.jar', 'dom4j.jar', 'jdbc2_0-stdext.jar', 'jta.jar', 'log4j.jar' und 'odmg.jar' und kopieren Sie die '.jar'-Dateien nach '<project-root>/lib' (die Dateinamen können etwas anders lauten oder Versionsnummern beinhalten).
  4. Erzeugen Sie im Verzeichnis '<project-root>/src' die folgende Datei 'log4j.properties':
    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.

  5. Erzeugen Sie im Verzeichnis '<project-root>/src' die folgende Hibernate-SessionFactory-Konfigurationsdatei 'hibernate.cfg.xml':
    <?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.

  6. Erzeugen Sie im Verzeichnis '<project-root>/src' die folgende Hibernate-Mapping-XML-Datei 'Kunde.hbm.xml':
    <?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.

  7. Erzeugen Sie im Verzeichnis '<project-root>/src' die folgende Hibernate-Mapping-XML-Datei 'Speise.hbm.xml':
    <?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.

  8. Erzeugen Sie im Verzeichnis '<project-root>/src' die folgende Hibernate-Mapping-XML-Datei 'Kommentar.hbm.xml':
    <?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'.

  9. Erzeugen Sie im Verzeichnis '<project-root>/src/mypackage' die folgende Datei 'Kunde.java' als übliche Entity-JavaBean mit 'private'-Attributen mit Gettern und Settern, mit einer dem Primary Key der Datenbanktabellenzeile entsprechendem ID-Attribut und mit weiteren der Datenbanktabelle entsprechenden Attributen (POJO, Plain Old Java Object, siehe http://www.hibernate.org/hib_docs/reference/en/html/persistent-classes.html):
    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; }
    }
    
  10. Erzeugen Sie im Verzeichnis '<project-root>/src/mypackage' die folgende Entity-JavaBean 'Speise.java':
    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; }
    }
    
  11. Erzeugen Sie im Verzeichnis '<project-root>/src/mypackage' die folgende Entity-JavaBean 'Kommentar.java':
    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; }
    }
    
  12. Erzeugen Sie im Verzeichnis '<project-root>/src/mypackage' das folgende Hauptprogramm 'Pizzeria.java':
    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.

  13. Erzeugen Sie im Verzeichnis '<project-root>' die folgende Datei 'build.xml':
    <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>
    
  14. Ö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"

  15. 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.)

  16. 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.



Transaktionen

Grundsätzliches zu Transaktionen

Bitte lesen Sie vorab die Erläuterungen zu Transaktionen unter: jee-transaktionen.htm.

Datenbank-Transaktionen

Hibernate unterstützt verschiedene Mechanismen für Datenbank-Transaktionen:

'Long Transactions'

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'.

'Optimistic 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':

'Pessimistic Locking'

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

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



Weitere Bemerkungen zu Hibernate



Links auf weiterführende Informationen





Weitere Themen: andere TechDocs | SQL mit Java | SQL
© 1998-2007 Torsten Horn, Aachen