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

+ andere TechDocs
+ JPA
+ Vererbung in SQL
+ SQL mit Java
+ SQL
+ hibernate.org
+


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



Inhalt

  1. Begriffsbestimmungen
    1. POJO, JavaBean, VO, TO, DTO, DAO
    2. Entity Object, Domain Object
    3. DAO/DTO- versus Domain-Object-Strategie
  2. Vorbemerkungen zu Hibernate
  3. 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
  4. Debugging
  5. Zugriffsmethoden
  6. Collections
  7. Speicherung der 'SessionFactory' und der 'Session'
    1. Application Server
    2. Webanwendung
    3. Session über ThreadLocal
  8. Transaktionen und Locking
    1. Grundsätzliches, Manuell, JDBCTransaction, JTATransaction
    2. Long Transaction, Pessimistic Locking, Optimistic Locking
  9. Caching
    1. First-Level-Cache
    2. Second-Level-Cache
  10. Weitere Bemerkungen zu Hibernate
  11. Links auf weiterführende Informationen


Begriffsbestimmungen

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.



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-Java-Klassen (POJO-JavaBeans) 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
speise Speise
text String
datum Date

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.

  1. Installieren Sie ein aktuelles Java SE JDK und ant, zum Beispiel wie beschrieben unter: java-install.htm#InstallationUnterWindows.
  2. 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
  3. Downloaden Sie das hSqlDb-Paket (z.B. hsqldb_1_8_0_9.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).
  4. Downloaden Sie das Hibernate3-Paket (z.B. hibernate-3.2.5.ga.zip) von http://www.hibernate.org. Extrahieren Sie daraus die Datei 'hibernate3.jar' und aus dem 'lib'-Verzeichnis die Dateien 'asm.jar', 'cglib-2.1.3.jar', 'checkstyle-all.jar', 'commons-collections-2.1.1.jar', 'dom4j-1.6.1.jar', 'jta.jar' und 'log4j-1.2.11.jar' und kopieren Sie die '.jar'-Dateien nach '<project-root>/lib' (die Dateinamen können etwas anders lauten oder andere Versionsnummern beinhalten).
  5. 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]
    
  6. 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.

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

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

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

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

  11. Erzeugen Sie im Verzeichnis '<project-root>/src/mypackage' die folgende Datei 'Kunde.java' als übliche Entity-POJO-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 (siehe auch http://www.hibernate.org/hib_docs/v3/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; }
    }
    
  12. 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; }
    }
    
  13. 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 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; }
    }
    
  14. Erzeugen Sie im Verzeichnis '<project-root>/src/mypackage' das folgende Hauptprogramm 'Pizzeria.java':
    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.

  15. 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>
    
  16. 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
    
  17. Ö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.

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



Debugging

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.



Zugriffsmethoden

  1. Unterscheiden Sie zwischen 'createQuery()', 'list()' und 'iterate()':
    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'.
  2. Unterscheiden Sie zwischen 'get()' und 'load()':
    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.
  3. Unterscheiden Sie folgende 'Session'-Speicherungsmethoden:
    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.


Collections

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


Speicherung der 'SessionFactory' und der 'Session'

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.



Transaktionen und Locking

Grundsätzliches zu Transaktionen

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

Transaktionsunterstützung in Hibernate

Hibernate unterstützt verschiedene Mechanismen für Transaktionen:

'Long Transaction' und Sperrstrategien

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.

'Pessimistic Locking'

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.

'Optimistic Locking'

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

Weiteres zu Transaktionen

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



Caching

First-Level-Cache

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.

Second-Level-Cache



Weitere Bemerkungen zu Hibernate



Links auf weiterführende Informationen





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