![]() |
|
URL dieses Artikels:
zu Ausgabe:
09.2002
Castor-Transport
Java-basiertes Object-Mapping für XML, RDBMS und LDAP
von Sebastian Eschweiler
Seit jeher steht Java als plattformunabhängige Programmiersprache zwischen den Welten der verschiedenen Computersysteme. Aber gerade diese Tatsache hat dazu beigetragen, dass eine Reihe von übergreifenden Standards geschaffen wurden, um sich mehr und mehr von der Plattformbindung zu lösen. Wer in seinen Java-Projekten auf XML, SQL oder LDAP setzt, findet in Castor ein Data-Bind ing-Framework, das alle Anforderungen dieser Bereiche vereint und ein optimales Zusammenspiel mit der Programmiersprache Java garantiert. Der folgende Artikel bietet eine einfache und verständliche Einführung in die Anwendung des Frameworks Castor.
Wer sich zur Aufgabe stellt, alle Funktionen des Castor Data-Binding-Frameworks in einem Satz zu nennen, wird daran vermutlich schnell scheitern. Bereits wer die Aufzählung der Überschrift zu diesem Artikel beachtet und Java-Objekte, XML-Dokumente, SQL-Tabellen und LDAP-Verzeichnisse kennt, wird schnell die große Funktionsvielfalt von Castor erahnen können. Zunächst lassen sich zwei grundlegende Bereiche des Frameworks unterscheiden:
Castor XML beschreibt zunächst allgemein den Teil von Castor, der sich mit der XML-Verarbeitung und der Abbildung von XML-Daten auf Java-Objekte beschäftigt. Dieser Teil des Frameworks lässt sich also als XML-Data-Binding-Tool beschreiben. Betrachten wir zunächst das Marshalling-Framework, welches Bestandteil von Castor XML ist. Hiermit erhalten Sie die Möglichkeit, Daten aus XML-Dokumenten in Java-Objekte bzw. Java- Objekte in XML-Dokumente zu überführen. Bevor Castor jedoch einsatzfähig ist, müssen wir zunächst die Installation des Frameworks ansprechen. Die entsprechenden Schritte sind denkbar einfach, da Castor im Wesentlichen aus einer einzigen Jar-Datei besteht, die lediglich in den Classpath eingebunden werden muss, damit die Castor-Funktionen zur Verfügung stehen. Das entsprechende Jar-Archiv ist auf der Webseite des Castor-Projekts [1] www.castor.org zu finden. Das Marshalling-Framework Das Marshalling-Framework kommt für die Umwandlung von Java-Objekten in XML und den umgekehrten Weg zum Einsatz. Hierdurch erhalten Sie einen Ansatz der über die Fähigkeiten von SAX und DOM hinausgeht und die betreffenden Daten nicht strukturell, sondern lediglich inhaltlich verwaltet. Zum Erzeugen der XML-Dokumente brauchen in diesem Fall keine Strukturen von Hand erzeugt werden - dies bleibt dem Castor-XML-Framework überlassen. Am besten lässt sich dieses Prinzip an einem einfachen Beispiel erläutern, welches zunächst die Umwandlung eines Java-Objekts in ein entsprechendes XML-Dokument vornimmt. Zunächst wird eine Java-Klasse benötigt, die ein Objekt in Form einer Klasse enthält, welches die zu speichernden Daten aufnimmt. Listing 1 zeigt hierzu eine Klasse mit dem Namen Film, welche drei Informationen über Filme enthält. Hierzu zählen der Filmtitel, das Erscheinungsjahr und die Filmlänge. Listing 1 public class Film implements java.io.SerializableWichtig ist hierbei die Implementierung eines public-Konstruktors und die Verwendung von getter- und setter-Methoden. Diese Vorgehensweise erinnert stark an die Programmierung von Java-Beans, die der gleichen Konvention entspricht. Eine Instanz der Klasse kann nun dem Marshaller übergeben werden, der sich anschließend um die Überführung der Datenstruktur in eine XML-Datei kümmert. Listing 2 verdeutlicht diese Vorgehensweise anhand eines Datensatzes vom Typ Film. Listing 2 public void Marshal()Die Methode marshal aus Listing 2 legt zunächst ein Objekt vom Typ FileWriter an. Dem Konstruktor wird hierbei der Name der XML-Datei übergeben, in die später der Datensatz des Films geschrieben werden soll. Ein Objekt vom Typ Film wird anschließend angelegt und mit den entsprechenden Daten gefüllt. Hierbei wird auf die setter-Methoden der Klasse Film zurückgegriffen. Der Aufruf des Marshallers mittels Marshaller.marshal(film, writer) kann nun erfolgenden. Hierzu müssen Sie wissen, dass die Klasse Marshaller aus dem Package org.exolab.castor.xml stammt. Hierzu muss das JAR-Archiv von Castor natürlich in den Classpath eingebunden werden. Die Methode marshal erfordert die Übergabe von zwei Parametern. Zum einen wird das Objekt zur Speicherung übergeben (im Beispiel Film) und zum anderen ein Objekt vom Typ FileWriter. Als Ausgabe sollte die Datei filme.xml nun folgenden Inhalt aufweisen: <?xml version="1.0"?>Mit diesem Beispiel lässt sich sehr gut verdeutlichen, dass sich die Anwendung des Castor-Frameworks sehr einfach gestaltet und praxistauglich ist. Der umgekehrte Weg lässt sich ebenso einfach durchführen und wird als Unmarshalling bezeichnet. Hierzu werden wir im Folgenden ein Objekt vom Typ Film aus der eben erzeugten XML-Datei generieren. Anstelle eines FileWriters wird in diesem Fall ein FileReader-Objekt eingesetzt, um die Datei filme.xml einlesen zu können: FileReader reader = new FileReader(new File(filme.xml));Die Klasse Unmarshaller aus dem Package org.exolab.castor.xml kann nun verwendet werden, um ein Film-Objekt aus der XML-Datei anzufordern. Hierzu wird erneut auf eine statische Methode dieser Klasse zugegriffen: Film film = (Film)Unmarshaller.unmarshal(Film.class, reader);Steuern des Mappings Der letzte Abschnitt hat Ihnen nun gezeigt, wie einfach die Übertragung von Daten zwischen Java-Objekten und XML-Dokumenten ist. Wir greifen mit der Marshaller- und Unmarshaller-Funktion bei den vorherigen Beispielen auf die Standardeinstellung von Castor zurück. Hierbei nehmen wir keinen Einfluss auf den Mapping-Vorgang und verlassen uns auf die Vorgaben von Castor. Wie Sie sich vielleicht vorstellen können, bietet das Castor-Framework auch in diesem Punkt Möglichkeit zur Anpassung. Alle gewünschten Einstellungen zur Transformation, die vom Standardverhalten Castors abweichen, werden in einer Mapping-Datei hinterlegt. Die Mapping-Datei muss dem Marshaller durch die Methode setMapping(Mapping mapping) mitgeteilt werden. Hierzu legen Sie zunächst ein Objekt vom Typ Mapping an (Listing 3). Listing 3 ... } catch (MappingException mp)Beachten Sie, dass beim Umgang mit Mapping-Objekten eine Exception vom Typ MappingException ausgelöst werden kann, die von Ihrem Programm abgefangen werden muss. Die Mapping-Datei (im Beispiel mapping.xml) wird durch die Methode loadMapping (String file) geladen und mit dem entsprechenden Mapping-Objekt verbunden. Listing 3 zeigt die Verwendung einer Mapping-Datei durch einen Marshaller. Ähnlich einfach gestaltet sich die Verwendung von Mapping-Files in Kombination mit einem Unmarshaller. Hierbei wird die Mapping-Datei beim Konstruktoraufruf übergeben: Unmarshaller unmar = new Unmarshaller(mapping);Bisher haben wir lediglich die Anwendung einer Mapping-Datei betrachtet, jedoch keine Erläuterung zum Erstellen und zur Syntax einer solchen Datei gegeben. Dies wird im folgenden Abschnitt nachgeholt. Aufbau von Mapping-Dateien Wie Sie bereits im Beispielprogramm gesehen haben, handelt es sich bei der Mapping-Datei um ein XML-Dokument. Der Aufbau ist sehr einfach gehalten und klar gegliedert. Das Root-Element der Mapping-Beschreibung ist grundsätzlich das Element >mapping/>. Innerhalb dieses Root-Elements können verschiedene Angaben gemacht werden, dazu zählen:
<?xml version=1.0?>Das Element <class name=Film>Dieses Beispiel behandelt die Eigenschaft Titel der Klasse Film. Über das Attribut name wird zunächst der Name der Klasseneigenschaft übergeben und das Attribut direct informiert Castor schließlich noch darüber, dass es sich bei Titel um ein public-Objekt handelt, welches direkt benutzt werden kann. Castor wird somit keine getter- und setter-Methoden anlegen. Der Typ des Objekts wird durch das Attribut type festgelegt. Hierbei kommt der vollständige Java-Klassenname zum Einsatz. Für die gebräuchlichsten Java-Klassen stehen außerdem Abkürzungen zur Verfügung, die anstelle des kompletten Klassennamens eingesetzt werden können. Hierzu finden Sie in der Castor-Dokumentation und auf der Castor-Homepage weitere Informationen. Innerhalb des field-Elements legen Sie weiterhin über >bind-xml/> fest, wie das beschriebene Feld der Klasse in die XML-Datei übertragen werden soll. Das Attribut name legt den Namen innerhalb des XML-Dokuments fest und node definiert, ob die Darstellung als Attribut oder Child-Element erfolgen soll. Dazu können die Werte attribute und element übergeben werden. Source-Generator Im letzten Abschnitt haben Sie nun die Möglichkeiten des Mappings in Castor kennengelernt. Mapping beschäftigt sich grundsätzlich mit der Transformation von Java-Objekten zu einer XML-Struktur. Wie Sie sicherlich wissen, kann jede XML-Datei durch ein entsprechendes Schema validiert werden. Castor bietet mit dem eingebauten Source Code-Generator die Möglichkeit, aus einem XML-Schema eine Java-Klasse zu generieren. Verdeutlichen lässt sich die Benutzung des Source Code-Generators an einem einfachen Aufruf über die Kommandozeile: java org.exolab.castor.builder.SourceGenerator -i schema.xsd -package de.fooDieser Aufruf analysiert zunächst die durch den Parameter -i übergebene Schema-Datei und generiert anschließend aus diesen Informationen die benötigten Klassen. Alle Klassen befinden sich anschließend im Package de.foo. Natürlich können die neu generierten Klassen mittels javac problemlos kompiliert werden. Wenn man die Funktionsweise des Source Code-Generators betrachtet, stellt sich natürlich die Frage, in welcher Weise die im Schema enthaltenen Funktionen ausgewertet werden, um die benötigten Informationen für eine Java-Klasse zu erhalten. Castor stellt Ihnen zwei unterschiedliche Methoden zur Klassengenerierung zur Verfügung. Zum einen kann der Source-Generator in Verbindung mit der so genannten element'-Methode verwendet werden und zum anderen kann die type'-Methode zum Einsatz kommen. Zunächst betrachten wir die element'-Methode. Jeder top-level complexType des Schemas wird bei dieser Methode in eine abstrakte Klasse transformiert. Es werden keine Klassen für simpleType-Einträge erstellt. Diese Vorgehensweise lässt sich am besten an einem einfachen Beispiel verdeutlichen. Zunächst betrachten wir im folgenden Listing eine Schema-Datei: <?xml version=1.0?>Das Schema legt zunächst einen complexType fest, der als Parameter den Namen der zu generierenden Klasse enthält. Innerhalb des complexType nimmt das Element Die Klasse, die aus diesem Schema nach der Generierung mit der element'-Methode entstehen würde, ist in Listing 4 abgedruckt. Listing 4 import java.io.Reader;Die generierte Klasse setzt die beiden Element-Deklarationen der Schema-Datei als Variablen um, die jeweils mit den entsprechenden getter- und setter-Methoden ausgestattet sind. Außerdem werden die entsprechenden Methoden für den Marshaller als abstract deklariert. Die Klasse selbst wird ebenfalls als abstract deklariert. Dieses Verhalten lässt sich leicht ändern, indem der Umwandlungstyp von element' auf type' geändert wird. Hierzu müssen Sie eine Zeile der Datei castorbuilder.properties ändern. Suchen Sie innerhalb dieser Datei die Zeile org.exolab.castor.builder.javaclassmapping=elementund ändern Sie den Wert auf type. Nun erhalten Sie eine implementierte Klasse, welche ohne abstract- Komponenten auskommt. Java Data Object API Wie eingangs besprochen, gehen wir nun zum zweiten Teil dieses Beitrags über und sehen uns - nach der Betrachtung der Castor XML-Funktionen - die Datenbankunterstützung mittels Java Data Objects an. Zunächst gibt es hierbei zwei unterschiedliche Anwendungsfälle zu unterscheiden: eine Datenbankverbindung kann in einer gewöhnlichen Client-Applikation vorkommen oder in einem J2EE Server-Umfeld. Im ersten Anwendungsfall muss eine Verbindung zur Datenbank mit Hilfe von JDBC von Hand aufgebaut werden. J2EE-Applikationen haben den Vorteil über JNDI Datenbankkonfiguration erfragen zu können. Somit brauchen hier keine Verbindungsparameter von Hand eingegeben werden. In Verbindung mit der Anwendung von Datenbankapplikationen bietet die J2EE-Plattform natürlich noch zahlreiche weitere Vorteile, auf die wir an dieser Stelle aber nicht weiter eingehen wollen. Vielmehr soll die weitere Betrachtung auf die Fähigkeiten des Castor-Frameworks in Verbindung mit den oben beschriebenen Anwendungsfällen gelenkt werden. Java Data Objects übernehmen nun das Mapping von Java-Objekten zu SQL-Tabellen. Die Funktionsweise besitzt zudem viele Parallelen zum zuvor besprochenen XML-Mapping. Sie brauchen sich nicht um die Struktur der Java-Objekte zu kümmern und die Eigenschaften der Klasse einzeln von Hand auf die Datenbanktabelle abzubilden. Diese Aufgabe übernimmt die JDO-Funktion von Castor automatisch. Hierbei ist die persistente Speicherung der Objekte zentrale Aufgabe. Persistente und transitorische Objekte Bevor wir uns nun komplett auf die Programmierung mittels JDO konzentrieren, klären wir zunächst einmal den Unterschied zwischen persistenten und transitorischen Objekten. Ein Optimalfall eines Datenbankmappings ist dann gegeben, wenn es sich um eine persistente Speicherung von Objekten handelt. Dies bedeutet, dass die in der Datenbank abgelegten Daten zu jedem Zeitpunkt mit den Daten des Objekts übereinstimmen müssen. Dieser Zustand kann insbesondere bei commit, rollback oder update-Befehlen an die Datenbank nicht mehr zutreffen. Die Objekte gehen dann in einen transitorischen Zustand über. Öffnen einer JDO-Datenbank-Verbindung Das Öffnen einer JDO-Datenbank-Verbindung ist mit wenigen Schritten vollzogen. Es werden die Klassen org.exolab.castor.jdo.JDO und org.exolab.castor.jdo.Database benötigt. Hierzu folgendes Listing: JDO jdo;Zunächst wird je ein Objekt vom Typ JDO und Database angelegt. Die Instanz des JDO-Objektes wird anschließend über den Aufruf der Methoden setDatabaseName(String str) und setConfiguration(String str) konfiguriert. Hier legen Sie Informationen zur Datenbankverbindung ab, die anschließend dazu benutzt werden können, eine Datenbankverbindung, die durch ein Objekt vom Typ Database repräsentiert wird, zu erhalten. Hierzu dient Ihnen die Methode jdo.getDatabase(). Das Beispiel zeigt, dass die Datenbankverbindung lediglich durch einen Verweis auf eine XML-Datei beschrieben wird. Im voran gegangenen Listingbeispiel ist dies die Datei dbconf.xml. Der folgende Abschnitt wird sich nun mit der Syntax dieser Datei beschäftigen und auf die Möglichkeiten der Konfiguration eingehen. Datenbank-Konfiguration Am besten lässt sich die Vorgehensweise der Konfiguration an einer einfachen Beispieldatei erkennen. Folgendes Listing zeigt hierzu eine Konfigurationsdatei für eine Oracle-Datenbank. <database name="media" engine="oracle">Bei der Konfigurationsdatei handelt es sich um ein XML-Dokument mit dem Root-Element Nach der allgemeinen Einstellung der verwendeten Datenbank müssen nun die Treiber und die Verbindungsparamter festgelegt werden. Dies geschieht innerhalb des Elementes Um nun eine Anfrage an die zuvor konfigurierte Datenbank zu stellen, ist ein SQL-Statement sicherlich nicht das Mittel der Wahl, da das Objekt-Mapping gerade den Sinn verfolgt, sich von der Darstellung der Daten als Tabelle zu lösen. Vielmehr ist an dieser Stelle eine Abfragesprache gefordert, die mit Java-Objekten umgehen kann und die Fähigkeit besitzt, auf dieser Basis Datenbankabfragen zu generieren. Wie Sie sich sicherlich denken können, existiert auch für diesen Bereich innerhalb des Castor-Frameworks eine Lösung: OQL. Bei OQL handelt es sich um eine Objekt-basierte Abfragesprache, die sich sehr eng an die Syntax von SQL anlehnt und daher für Umsteiger optimal geeignet ist. Wer zuvor keine SQL-Statements kennen gelernt hat, wird aber ebenfalls keine Probleme besitzen den Grundgedanken dieser Sprache zu verstehen. Grundlegende Klassen für die Arbeit mit OQL in Castor sind OQLQuery und QueryResult. Ein OQLQuery-Objekt ist sehr einfach über ein zuvor angelegtes Database-Objekt zu initialisieren: OQLQuery myquery;Der Methode getOQLQuery wird ein String übergeben, der die Anfrage darstellt. Der Hauptunterschied zu SQL ist hierbei die Tatsache, dass als Basis ein Objekt (im Beispiel das Objekt Film) und dessen Eigenschaften (preis) dienen. Ist erst einmal die Anfrage erstellt und in Form des OQLQuery-Objektes vorhanden, kann nun das Ergebnis mit Hilfe der Methode execute() erfragt werden. Rückgabewert ist in diesem Fall ein QueryResult. QueryResult result = myquery.execute();Eine einfache Schleife reicht anschließend aus, um die Resultate der Reihe nach zu durchlaufen. while (result.hasMore())Wie Sie Objekte aus einer Datenbank erhalten, haben Sie nun gesehen. Was ist aber zu tun, um neue Objekte in eine Datenbank aufzunehmen oder aus dem Datenbestand zu entfernen? Auch diese Aufgabe ist denkbar einfach und über zwei einfache Methoden der Klasse Database realisiert: create(java.lang.Object) und remove(java.lang.Object). Hierbei wird jeweils das Objekt übergeben, mit dem die jeweilige Operation durchgeführt werden soll. Mittels dieser Methoden können die Objekte zwischen den Zuständen transitorisch und persistent überführt werden. Fazit Dieser Artikel hat einen ersten Einblick in das Framework Castor gegeben. Sie werden sicherlich festgestellt haben, dass die Funktionen, die Castor bietet, sehr vielfältig sind und für zahlreiche Anwendungsgebiete Lösungen bereit halten. Auf der Basis von Java ebnet Castor den einheitlichen Weg zu verschiedenen Standards der Datenspeicherung. Das zentrale Anwendungsgebiet des Object-Mappings wird durch eine Reihe von Zusatzfunktionen abgerundet, die Castor einzigartig erscheinen lassen. Gerade im Bereich der objektorientierten Programmierung ist es von zentraler Bedeutung bei der Verwaltung von großen Datenmengen nicht die Objektorientierung zu verlassen. Eine einheitliche Struktur wird wesentlich zu der Effektivität einer Anwendung beitragen. Castor gibt Ihnen alle Mittel mit auf den Weg, die Sie für die Lösung dieser Probleme brauchen. Wie eingangs erwähnt, ist dieser Beitrag noch nicht auf Anwendungsfälle des Castor-Projekts in Verbindung mit LDAP-Verzeichnissen eingegangen. Zu diesem Thema finden Sie einen eigenen Artikel in einer der nächsten Ausgaben des Java Magazins. Links und Literatur [1] www.castor.org |
||
|