Artikel

 
Juli 2004 | Artikel

Winterschläfer

(Link zum Artikel: http://www.it-republik.de/jaxenter/artikel/0596)

Objektrelationales Mapping mit Hibernate

Text: von Michael Plöd
Hibernate ist ein Open Source-Tool zum objektrelationalen Mapping von Datenbanken, welches sich in letzter Zeit immer größerer Beliebtheit erfreut. Seit September 2003 gehört das Hibernate-Projekt zur JBoss Group. Im Rahmen dieses Artikels soll ein Überblick über Hibernate anhand eines Beispielprojektes gegeben werden. Des Weiteren werden unterschiedliche Herangehensweisen für Projekte mit Hibernate, einige Design-Patterns sowie Tipps zur Verbesserung der Performance dargestellt.

Einführung in Hibernate
Hibernate ist ein Open Source-Persistenz-Tool, welches basierend auf XML-Konfigurationsdateien, so genannten Mappings, das Bindeglied zwischen JavaBeans und einer Datenbank darstellt. Aktuell werden 16 Datenbanken unterstützt, dazu zählen unter anderem Oracle, DB2, MySQL sowie PostgreSQL. Zu den weiteren Features zählen die Hibernate Query Language, Native SQL Queries sowie Lazy- und Outer-Join Fetching zur Steigerung der Performance. Zudem lässt sich Hibernate problemlos in alle gängigen J2EE Application Server integrieren. Neben den technischen Features glänzt Hibernate durch sehr regelmäßige Releases und eine Dokumentation, die ihresgleichen in der Open Source-Welt sucht. Zudem ist Hibernate sehr leicht zu verstehen und zu erlernen - einen ersten Schritt in diese Richtung soll dieser Artikel machen.
Das Projekt: CD-Datenbank
Die Grundlage dieses Artikels wird eine CD-Datenbank sein, die in ähnlicher Form beim nächsten Release des Online-Magazins Allschools Network zum Einsatz kommen wird. Die Struktur der verwendeten Datenbank ist in Abpictureung 1 dargestellt. Wie zu sehen ist, enthält die Datenbank sowohl 1 : 1-, 1 : n- und n : m-Beziehungen. Als Datenbank wurde für dieses Beispiel PostgreSQL in der Version 7.4 verwendet, was aufgrund der Flexibilität von Hibernate unerheblich ist, wie Sie im weiteren Verlauf des Artikels sehen werden.
Vorgehensweisen beim objektrelationalen Mapping mit Hibernate
Aufgrund zahlreicher Tools, die für Hibernate verfügbar sind, haben Sie vier verschiedene Möglichkeiten, Ihr Projekt mit Hibernate zu beginnen:
  • Top-down: Beginn mit JavaBeans
  • Bottom-up: Beginn mit dem relationalen Schema
  • Middle-out: Beginn mit Mapping
  • Meet-in-middle: Beginn mit JavaBeans und dem Schema

Diese Herangehensweisen werden durch unterschiedliche Tools unterstützt, welche in Abpictureung 2 dargestellt sind. Bei der Top-down-Vorgehensweise wird mit dem Schreiben der JavaBeans begonnen. Diese werden danach entweder mit XDoclet Tags versehen oder Sie schreiben die Mappings für Hibernate selbst. Abschließend wird die Beschreibung des Datenbank-Schemas mithilfe des Programms hbm2ddl, welches Bestandteil von Hibernate ist, generiert und in die Datenbank exportiert.
Im Gegensatz dazu beginnt die Bottom-up-Vorgehensweise entweder mit einer bestehenden Datenbank oder deren Definition. Danach werden die Mapping-Dateien entweder durch das Hibernate-Plugin von Middlegen generiert oder selbst geschrieben. Optional können Sie noch manuelle Änderungen an den Dateien vornehmen. Abschließend werden die Sourcen für die JavaBeans mit dem Tool hbm2java, welches ebenfalls Bestandteil von Hibernate ist, generiert.
Der Middle-out-Ansatz beginnt mit den Hibernate Mapping-Dateien, was bedeutet, dass Sie zuerst die Hibernate-spezifischen XML-Dateien schreiben und danach das Datenbank-Schema mit hbm2ddl und die Java-Sourcen mit hbm2java generieren.
Der Meet-in-middle-Ansatz ist die wahrscheinlich schwerste und riskanteste der oben genannten Vorgehensweisen. Sie beginnen nämlich gleichzeitig mit den Java-Sourcen und der Definition des Datenbank-Schemas. Danach schreiben Sie die Hibernate Mappings und passen abschließend die Sourcen und/oder das Datenbank-Schema an.
Da die meisten Entwickler mit einer bestehenden Datenbank konfrontiert sind, wird der weitere Verlauf des Artikels der Bottom-up-Vorgehensweise folgen.
Objektrelationales Mapping mit Hibernate
Damit Ihre JavaBeans über Hibernate mit der Datenbank kommunizieren können, muss der Zugriff über die oben angesprochenen XML Mapping-Dateien konfiguriert werden. Hierbei ist es empfehlenswert, pro JavaBean eine gesonderte XML-Datei zu verwenden, somit bleiben auch größere Projekte wartbar.
Eine Hibernate Mapping-Datei beginnt immer mit dem XML Tag . In diesem kann optional das Default-Datenbank-Schema Ihrer Applikation übergeben werden. Innerhalb des Hibernate Mapping-Elements können Sie eine oder mehrere Klassen durch das Tag definieren.
  1. <hibernate-mapping schema="allschools">
  2. <class name="net.allschools.hbn.manual.Comment" table="COMMENT">
  3. ...
  4. </class>
  5. <class ...>
  6. ...
  7. </class>

Die Klasse Comment wird im oben gezeigten Beispiel auf die Tabelle COMMENT im Datenbank-Schema allschools gemappt. Der Parameter name verweist auf die JavaBean und table auf die entsprechende Tabelle. Neben dieser Angabe benötigt Hibernate Informationen, wie die einzelnen Variablen der Bean in der Tabelle abgepictureet werden, wie der Private Key der Tabelle erstellt und in welcher Spalte er gespeichert wird. Falls die Tabelle in Relation zu anderen Tabellen steht, muss dies ebenfalls innerhalb des Class-Element definiert werden. Eine JavaBean, die mit Hibernate auf eine Datenbank abgepictureet wird, muss einen Private Key haben. Dieser wird über das Tag konfiguriert. Innerhalb des Id-Elements muss noch ein Generator für die Erstellung neuer Primärschlüssel angegeben werden:
  1. <class name="net.allschools.hbn.manual.Comment" table="COMMENT">
  2. <id name="id" column="ID" type="integer">
  3. <generator class="sequence">
  4. <param name="sequence">allschools.comment_id_seq</param>
  5. </generator>
  6. </id>
  7. </class>

Der oben eingeführte Kommentar hat einen Primärschlüssel, welcher in der JavaBean durch die Variable id und in der Datebank durch die Spalte ID in der Tabelle COMMENT gespeichert wird. Zum Erstellen des Schlüssels wird die Sequence allschools.comment_id_seq verwendet. Hibernate hat zehn Generatoren für das Erstellen von Private Keys. Zum Beispiel:
  • HI/LO-Algorithmus
  • ID-Spalten und Sequences
  • von der Applikation zugewiesene Schlüssel

Neben den Primärschlüsseln muss die datenbankseitige Abpictureung der Variablen der JavaBean durch das Tag definiert werden.
  1. ...
  2. <id ... >
  3. ...
  4. </id>
  5. <property name="title" column="TITLE" type="string" not-null="false"/>
  6. <property name="text" column="COMMENT_TEXT" type="string" not-null="true"/>
  7. ...

Bei der Definition der Properties wird nach demselben Schema verfahren, welches schon bei der Definition der Primary Keys und der Klassen zum Einsatz kam. Der Parameter name definiert die Java-Welt und column die Datenbankseite. Neu bei den Properties ist die Angabe des Typs. Hierbei handelt es sich um Hibernate-spezifische Typdefinitionen von Variablen, welche sich aber an den Java-Typen orientieren. Der Parameter not-null gibt an, ob das entsprechende Feld gefüllt sein muss, bevor es in der Datenbank gespeichert wird. In unserem Beispiel wird der Titel des Kommentars in der Spalte TITLE gespeichert und kann null sein. Im Gegensatz dazu muss der Text des Kommentars mit einem String gefüllt sein, bevor er in der Spalte COMMENT_TEXT der Tabelle COMMENT abgelegt wird.
Da wir nun die Klassen auf die Datenbank abgepictureet haben, müssen wir diese in Relation zueinander bringen. In unserem Beispiel gibt es gemäß dem Datenbank-Schema in Abpictureung 1 folgende Relationen: eine n : m-Beziehung zwischen einer CD und den Bands, eine 1 : n-Beziehung zwischen einer CD und den Kommentaren und eine 1 : 1-Beziehung zwischen einer CD und dem Cover.
Die einfachste Beziehung ist die 1 : 1-Beziehung. Diese wird in der Hibernate Mapping-Datei durch das Tag abgepictureet. Folgender Eintrag in der Deklaration für die Releases verbindet eine CD mit der entsprechenden Bilddatei über eine 1 : 1-Beziehung:
  1. <one-to-one name="cover" class="net.allschools.hbn.manual.Image" cascade="none"/>

Beim Mapping von One-to-one-Relationen erfolgt keine Deklaration des Fremdschlüssels. Die Verbindung wird über den class-Parameter hergestellt, welcher auf die Klasse verweist, mit der die Beziehung hergestellt werden soll. Besondere Beachtung sollte der Angabe geschenkt werden, die unter cascade gemacht wird. cascade bestimmt, wie Updates und Deletes an Child-Elemente weitergereicht werden. Ist cascade auf all gesetzt, werden alle Operationen, inklusive Delete, auch an die Child-Elemente weitergeleitet.
Ähnlich wie bei 1 : 1-Beziehungen wird mit dem Mapping von 1 : n-Beziehungen verfahren. Um die Klasse Comment nun mit der Release-Klasse zu verbinden, müsste in der Hibernate Mapping-Datei von Comment folgender Eintrag stehen:
  1. <many-to-one name="release" column="RELEASE_ID" class="net.allschools.hbn.manual.Release"/>

Bei der Many-to-one-Deklaration ist nur die Angabe des Namens der Variable verpflichtend. Hibernate ist in der Lage, die Klasse, auf die verwiesen wird, mittels Reflection zu bestimmen. Auch die Angabe des Fremdschlüsselfeldes ist optional. Aus Gründen der Lesbarkeit ist es dennoch vor allem bei großen Projekten ratsam, diese Angaben zu machen.
Im Gegensatz zu den oben angesprochenen Relationen verweist bei One-to-many- und Many-to-many-Beziehungen mindestens ein Element auf mehrere Elemente. Deshalb muss hier, auch im Java-Code, mit Collections gearbeitet werden. Hibernate unterstützt folgende Collections: Set, SortedSet, Map, SortedMap und List. Da eine CD mehrere Bemerkungen von den Usern bekommen kann, ist in der Release-Klasse eine Collection nötig, die Objekte von Typ Comment enthält. In der Hibernate Mapping-Datei sieht diese Collection, in unserem Fall ein Set, wie folgt aus:
  1. <set name="comments" cascade="none" lazy="true">
  2. <key column="release_id"/>
  3. <one-to-many class="net.allschools.hbn.manual.Comment"/>
  4. </set>

Das Tag definiert die Collection. Aus Performancegründen ist bei großen Datenbanken anzuraten, die Eigenschaft lazy auf true zu setzen. Dadurch wird der Inhalt der Collection erst dann geladen, wenn dieser wirklich benötigt wird. Innerhalb des Set-Elements muss zum einen mit dem Tag ein Verweis auf den Fremdschlüssel in der Tabelle des Child-Elements stehen, zum anderen muss noch die Art der Beziehung definiert werden. Im oben beschriebenen Fall hat die Tabelle COMMENT mit der Spalte RELEASE_ID einen Fremdschlüssel, der auf RELEASE weist. Deshalb steht im Key-Element release_id als Spalte. Des Weiteren soll eine One-to-many-Beziehung zur Klasse Comment hergestellt werden.
Im Fall von Many-to-many-Beziehungen müssen in den Hibernate Mapping-Dateien weitere Angaben gemacht werden. In unserem Beispiel besteht in der Datenbank zwischen den Releases und den Bands eine n : m-Beziehung, welche über die RELEASE_BANDS-Tabelle hergestellt wird. In der Mapping-Datei der Release-Klasse sind somit folgende Angaben nötig:
  1. <set name="bands" table="RELEASE_BANDS" cascade="none" lazy="true">
  2. <key column="RELEASE_ID"/>
  3. <many-to-many class="net.allschools.hbn.manual.Band" column="BAND_ID"/>
  4. </set>

Neben den Angaben zum Namen der Variable, dem Cascading und der Lazy Initialization ist die Tabelle anzugeben, die die n : m-Verbindung herstellt. Die Angabe zum Fremdschlüssel, der auf die gerade gemappte Tabelle verweist, ist gleich wie bei einer One-to-many-Beziehung, mit der Ausnahme, dass sich der Fremdschlüssel in der Mapping-Tabelle befindet. Im Tag ist die Klasse anzugeben, mit der die Beziehung hergestellt werden soll, zudem ist noch deren Fremdschlüssel in der Mapping-Tabelle zu definieren.

Listing 1
  1. <?xml version="1.0"?>
  2. <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
  3. <hibernate-mapping schema="allschools">
  4. <class name="net.allschools.hbn.manual.Image" table="IMAGE">
  5. <id name="id" column="ID" type="integer">
  6. <generator class="sequence">
  7. <param name="sequence">allschools.image_id_seq</param>
  8. </generator>
  9. </id>
  10. <property name="title" column="TITLE" type="string" not-null="true"/>
  11. <property name="path" column="PATH" type="string" not-null="true"/>
  12. </class>
  13. <class name="net.allschools.hbn.manual.Band" table="BAND">
  14. <id name="id" column="ID" type="integer">
  15. <generator class="sequence">
  16. <param name="sequence">allschools.band_id_seq</param>
  17. </generator>
  18. </id>
  19. <property name="name" column="NAME" type="string" not-null="true"/>
  20. <set name="releases" table="RELEASE_BANDS" lazy="true">
  21. <key column="BAND_ID"/>
  22. <many-to-many class="net.allschools.hbn.manual.Release" column="RELEASE_ID"/>
  23. </set>
  24. </class>
  25. <class name="net.allschools.hbn.manual.Release" table="RELEASE">
  26. <id name="id" column="ID" type="integer">
  27. <generator class="sequence">
  28. <param name="sequence">allschools.release_id_seq</param>
  29. </generator>
  30. </id>
  31. <property name="title" column="TITLE" type="string" not-null="true"/>
  32. <property name="releaseDate" column="release_date" type="date" not-null="false"/>
  33. <one-to-one name="cover" class="net.allschools.hbn.manual.Image" cascade="all"/>
  34. <set name="comments" cascade="all" lazy="true">
  35. <key column="release_id"/>
  36. <one-to-many class="net.allschools.hbn.manual.Comment"/>
  37. </set>
  38. <set name="bands" table="RELEASE_BANDS" cascade="all" lazy="true">
  39. <key column="RELEASE_ID"/>
  40. <many-to-many class="net.allschools.hbn.manual.Band" column="BAND_ID"/>
  41. </set>
  42. </class>
  43. <class name="net.allschools.hbn.manual.Comment" table="COMMENT">
  44. <id name="id" column="ID" type="integer">
  45. <generator class="sequence">
  46. <param name="sequence">allschools.comment_id_seq</param>
  47. </generator>
  48. </id>
  49. <property name="commentDate" column="COMMENT_DATE" type="date" not-null="true" />
  50. <property name="title" column="TITLE" type="string" not-null="true"/>
  51. <property name="text" column="COMMENT_TEXT" type="string" not-null="true"/>
  52. <property name="userName" column="USER_NAME" type="string" not-null="true"/>
  53. <many-to-one name="release" column="RELEASE_ID" class="net.allschools.hbn.manual.Release"/>
  54. </class>
  55. </hibernate-mapping>
Anbindung von Hibernate in Applikationen
Da wir nun mit dem Mapping der Datenbank fertig sind, können wir mit der Integration von Hibernate in eine Anwendung beginnen. Aus Sicht der Applikation sind folgende Klassen aus dem Hibernate Package essenziell:
  • SessionFactory
  • Session
  • Transaction

Die SessionFactory lädt und hält alle Mappings und ist eine Factory für die Klasse Session. Des Weiteren ist sie in der Lage, als Daten-Cache zwischen Transaktionen zu fungieren. Am wichtigsten jedoch ist, dass die SessionFactory thread-safe ist. Die Session wird von der SessionFactory erstellt und ist das Bindeglied zwischen der Datenbank und der Applikation. Sie ist zudem eine Factory für Transaction-Instanzen und nicht thread-safe.
JDBC-, JTA- oder CORBA-Transaktionen werden durch die Hibernate-Klasse Transaction abgepictureet. Wie oben angesprochen, werden Instanzen der Klasse Transaction über die Session erstellt. Innerhalb einer Session können allerdings mehrere Transaktionen gestartet werden.
Die SessionFactory kann direkt aus der Anwendung über XML- oder mittels Property-Dateien konfiguriert werden. Ich werde mich in diesem Tutorial auf die XML-Konfiguration beschränken. Der Standardname für die Konfigurationsdatei ist hibernate.cfg.xml, es ist aber durchaus möglich, aber nicht ratsam, andere Namen zu verwenden. Für unser Beispiel könnte diese XML-Datei wie in Listing 2 aussehen:

Listing 2
  1. <?xml version='1.0' encoding='utf-8'?>
  2. <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">
  3. <hibernate-configuration>
  4. <session-factory name="java:comp/env/hibernate/SessionFactory">
  5. <!-- properties -->
  6. <property name="hibernate.connection.driver_class">org.postgresql.Driver</property>
  7. <property name="hibernate.connection.url">jdbc:postgresql:allschools</property>
  8. <property name="hibernate.connection.username">username</property>
  9. <property name="hibernate.connection.password">passwort</property>
  10. <property name="hibernate.connection.pool_size">5</property>
  11. <property name="dialect">net.sf.hibernate.dialect.PostgreSQLDialect</property>
  12. <property name="show_sql">true</property>
  13. <property name="use_outer_join">true</property>
  14. <!-- mapping files -->
  15. <mapping resource="Mappings.hbm.xml"/>
  16. </session-factory>
  17. </hibernate-configuration>

Wir konfigurieren Hibernate so, dass es einen eigenen Connection Pool mit fünf Verbindungen zu einer PostgreSQL-Datenbank aufbaut. Der Datenbankzugriff wird durch die hibernate.property.*-Einträge konfiguriert. Da jede Datenbank ihre eigenen Feinheiten hat, was die Implementierung des SQL-Standards anbelangt, muss Hibernate noch mitgeteilt werden, auf welches Datenbankprodukt zugegriffen wird. Diese Angabe muss im Property mit dem Namen dialect gemacht werden. Anbei ist eine kleine Liste möglicher Dialects, die alle im net.sf.hibernate.dialect Package zu finden sind:
  • DB2: DB2Dialect
  • Oracle generell: OracleDialect
  • Oracle 9: Oracle9Dialect
  • MySQL: MySQLDialect
  • PostgreSQL: PostgreSQLDialect

Über das Property show_sql kann die Ausgabe der generierten SQL Statements eingeschaltet werden. Abschließend müssen Hibernate noch sämtliche Mapping-Dateien mitgeteilt werden. In der Applikationslogik wird die SessionFactory durch folgenden Aufruf geladen:
  1. try {
  2. SessionFactory sf = new Configuration().configure().buildSessionFactory();
  3. } catch (HibernateException e) {
  4. e.printStackTrace();
  5. }

Wie oben angesprochen, erzeugt die SessionFactory Session-Instanzen, die wiederum als Factory für Transactions dienen:
  1. Session sess = sf.openSession();
  2. Transaction trx = sess.beginTransaction();
  3. ...
  4. trx.commit();
  5. sess.close();

Wie bereits erwähnt, ist die Session das Bindeglied zwischen Datenbank und Applikationslogik. Sie ist somit die Grundlage für Delete-, Update-, Insert-Operationen und für Queries. Die Implementierung dieser Operationen in Hibernate wurde sehr einfach gehalten und läuft immer nach dem gleichen Schema ab: Sie erstellen oder laden zuerst ein Objekt, führen unter Umständen Änderungen über die Set- und Get-Methoden durch und führen abschließend durch die Session die gewünschte Operation aus.
Angenommen, wir wollen in unserer CD-Datenbank die Band mit dem Namen deCON aufnehmen. Der Java Code muss wie folgt aussehen:
  1. ...
  2. Band band = new Band();
  3. band.setName("deCON");
  4. session.save(band);
  5. ...

Für eine Update-Operation muss die Band zuerst mittels der load()-Methode geladen werden. Diese Methode erwartet neben dem Namen der Klasse, in unserem Fall Band.class, auch den entsprechenden Primärschlüssel der jeweiligen Band. Da von der Lade-Routine der Typ Object zurückgegeben wird, ist ein Cast auf die eigentliche Klasse nötig.
  1. ...
  2. Band band = (Band)session.load(Band.class, primaryKey);
  3. band.setName("de-CON");
  4. session.update(band);
  5. ...

Abschließend wollen wir die Band wieder aus der Datenbank entfernen. Dafür wird sie wieder, wie oben beschrieben, geladen und schließlich mit delete() gelöscht:
  1. ...
  2. session.delete(band);
  3. ...

Neben den Datenmanipulationsfunktionen bietet Hibernate auch die Möglichkeit, Abfragen an die Datenbank zu schicken. Eine komplette Erläuterung aller Features der Hibernate Query Language (HQL) würde den Rahmen eines kompletten Artikels sprengen, deshalb stelle ich in diesem Rahmen nur die grundsätzliche Funktionsweise dar. HQL kann als objektorientierte Version von SQL betrachtet werden und hat somit zahlreiche Parallelen. HQL selbst ist bis auf Object- und Package-Namen case-insensitive. Folgender Code-Ausschnitt selektiert alle Releases der oben erstellen Band de-CON in einen Iterator:
  1. String hql = "select elements(band.releases) from Band as band where band.name = :band_name";
  2. Query query = session.createQuery(hql);
  3. query.setString("band_name", "de-CON");
  4. Iterator i = query.iterate();

Der einzige Unterschied in der oben gezeigten HQL-Abfrage zu herkömmlichem SQL besteht in der elements-Syntax, welche ein Wrapper für Queries auf Collections ist. In HQL selektieren Sie zudem nicht von Tabellen, sondern von Klassen. Das heißt, dass wir im oben erwähnten HQL Statement von der Klasse und nicht von der Tabelle Band selektieren.
Performance-Tuning und Best Practices
Hibernate ist zwar sehr einfach zu erlernen, allerdings gibt es ein paar Punkte, die Sie beachten sollten, um eine gute Performance und Wartbarkeit des Persistenz-Layers zu erzielen. Generell ist es vor allem bei großen Projekten ratsam, pro Klasse eine Mapping-Datei anzulegen, um die Übersicht zu wahren. Des Weiteren ist es vor allem in Mulithread-Applikationen unabdingbar, die Session in einem ThreadLocal abzulegen, da Hibernate die Verbindung zwischen Session und Transaktion nicht an den aktuellen Thread bindet. Somit stellen Sie sicher, dass ein Thread nur die Daten sieht, die auch wirklich für ihn bestimmt sind.
Um überflüssige Datenbankabfragen zu minimieren, sollte man beim Mapping von Collections den Parameter lazy auf true setzen. Da die SessionFactory thread-safe implementiert ist, muss Sie für eine Applikation auch nur einmal instanziiert werden und nicht bei jedem Request. Die Performance von Queries lässt sich durch den Einsatz von Bind-Variablen, wie in der vorgestellten HQL Query gezeigt, beschleunigen. Abschließend ist festzustellen, dass es in einigen Situationen durchaus angebracht ist, auch handgeschriebenen JDBC-Code einzusetzen, allerdings sollte man davor sicher sein, dass auch ein Bottleneck vorliegt, welches sich mit direktem JDBC beseitigen lässt.
Fazit
Hibernate ist durchaus als ernst zu nehmende Konkurrenz zu anderen Persistenz-Tools wie OJB, Torque oder JDO zu sehen. Dies liegt zum einen an der leichten Erlernbarkeit und den zahlreichen Features, zum anderen an den zügigen Release-Zyklen und dem engagierten Projekt-Management des Teams um Gavin King und Christian Bauer. Abschließend ist noch einmal die umfassende Dokumentation hervorzuheben, die kaum Fragen offen lässt und zahlreiche Code-Beispiele bietet.
Michael Plöd arbeitet als Dipl.-Wirtschaftsinformatiker bei der Deutschen Bank AG. Der Schwerpunkt seiner Arbeit liegt im Bereich Java-Webapplikationen und J2EE. Sein besonderes Interesse gilt Persistenz-Frameworks.
Links und Literatur


Anzeige

Kommentare


Anzeige

zurück zum Seitenanfang