Dieses Kapitel führt in drei Zugriffsschnitstellen auf Datenbanken und persistente Objektspeicher ein.
Die Darstellung skizziert daher zunächst die auf das relationale Speicherungs- und Zugriffsparadigma
ausgelegte JDBC-Schnittstelle.
Davon ausgehend wird die Kapselung von JDBC-basierter Persistenzlogik durch
Enterprise Java Beans entwickelt. Hierbei steht die Ausprägungsform der bean managed persistence im
Vordergrund, da sie die weitestgehenden Eingriffsmöglichkeiten für den Programmierer bietet.
Abschließend wird mit den Java Data Objects ein jüngerer Ansatz zur Realisierung transparenter
Speicherung eingeführt, der gleichzeitig verschiedenste Persistenzdienstleiter unterstützt.
Häufig besteht der Wunsch oder die Notwendigkeit, auf
bereits vorliegende Datenbestände, die durch ein
Datenbankmanagementsystem (DBMS) verwaltet werden, in einer Applikationsprogrammiersprache
zuzugreifen. Dabei soll die Anbindung der benötigten Datenquelle
nicht problemspezifisch wieder und wieder neu entwickelt
werden, sondern sollte sich auf ähnliche
Datenanbindungsprobleme übertragen lassen.
Vor diesem Hintergrund liegt es nahe, sich an den Typen der
verfügbaren und kommerziell bedeutsamen
DBMS zu orientieren und
herstellerspezifische Entwicklungen außer Acht zu lassen.
Gleichzeitig offenbaren sich hierbei
Standardisierungsbemühungen wie die Sprache SQL zum Zugriff
auf relationale DBMS als lohnenswerter Ansatz der
Etablierung einer generischen und übertragbaren
Schnittstelle.
Die Idee zur Schaffung einer solchen generischen Schnittstelle für den Zugriff auf relationale DBMS geht zurück auf eine Initiative der SQL Access Group, welche später in der Vereinigung mit der X/Open Group aufging, die zwischenzeitlich in Open Group umbenannt wurde. Das dort konzipierte programmiersprachenunabhängige SQL Call Level Interface (SQL/CLI) konnte sich dank der Umsetzung unter dem Namen Open Database Connectivity (ODBC) durch die Firma Microsoft und die parallel erfolgte internationale Normierung unter dem Titel SQL/CLI breit am Markt etablieren.
Die für die Programmiersprache Java adaptierte Variante des Zugriffs auf relationale DBMS wird durch SUN Microsystems unter dem Namen Java Database Connectivity (JDBC) propagiert und stellt eine auf ODBC konzeptionell aufbauende und auf die spezifischen Bedürfnisse dieser Applikationsprogrammiersprache optimierte Untermenge des SQL/CLI-Standards dar.
Von den Vorgängeransätzen übernommene Grundidee der Schnittstelle ist es den physischen Zugriff auf das Datenbankmanagementsystem durch eine von der Applikation spearierte wiederverwendbare Softwarekomponente, den sog. JDBC-Treiber, abzuwickeln.
Dieser Treiber vermittelt zwischen der Javaapplikation und dem verwendeten DBMS. Hierbei muß für jedes DBMS ein auf es abgestimmter JDBC-Treiber verwendet werden, da lediglich die Schnittstelle zur Applikation, nicht jedoch die zum DBMS, standardisiert ist.
Diesem Treiber obliegt die Abwicklung der gesamten
Kommunikationsvorgänge mit dem DBMS. Er setzt jedoch
selbst keine datenbankspezifischen Funktionalitäten, wie
Syntax- oder Plausibilitätsprüfungen der übermittelten
Kommandos um. Etwaige Fehlerprüfungen können, ebenso wie Anfrageoptimierungen,
daher erst seitens des DBMS vorgenommen werden.
Der Vorteil dieses Vorgehens liegt in der Generizität des
JDBC-Treibers. Er kann ohne aufwendige Logikanteile als
reine uninterpretierende Vermittlungsschicht zwischen Applikation
und DBMS umgesetzt werden, wodurch schlanke Implementierungen ermöglicht werden.
Die JDBC-Spezifikation detailliert den Treiberbegriff zusätzlich hinsichtlich der gewählten technischen Umsetzung aus. So werden die vier in Abbildung 1 dargestellten Treibertypen gemäß ihrer Charakteristika beschrieben und unterschieden.
Die historisch älteste Variante bildet der Typ 1
Treiber. Strenggenommen verkörpert er selbst keinen
Datenbanktreiber, sondern lediglich eine Umsetzungsschicht
die einem existierenden ODBC-Treiber vorgeschaltet
wird.
Die Abbildung belegt diesen Treibertyp daher mit dem
Begriff JDBC-ODBC-Bridge, da er lediglich den
Brückenschlag zwischen den beiden Standards vornimmt und
sich in der konkreten Anwendung auf die Umsetzung
zwischen den beiden Protokollen beschränkt, ohne realen
Zugriff auf die Datenbank zu erhalten.
Dieser ist dem ODBC-Treiber vorbehalten, der im allgemeinen Falle mit einer weiteren Umsetzungsstufe
kommuniziert, welche die generischen ODBC-Aufrufe in konkrete DBMS-spezifische wandelt.
Während sowohl der JDBC-ODBC-Brückentreiber als auch der
ODBC-Treiber selbst für verschiedene DBMS verwendet werden
können, muß für jedes konkrete DBMS eine
herstellerspezifische, d.h. an das verwendete DBMS
angepaßte, Bibliothek vorliegen.
Für den Fall eines Typ 2 Treibers entfällt
diese durch ODBC geschaffene zusätzliche Indirektionsstufe
zugunsten der Adaption der Konversionskomponente, welcher
die Wandlung der Aufrufe in das DBMS-native Protokoll
obliegt, an das JDBC-Protokoll und ihrer Integration in den JDBC-Treiber
selbst.
Die Natur der Kommunikation des Java-Anteils des Treibers mit den
Nativen ist im Rahmen der durch die JDBC-Spezifikation
gegebenen Definition nicht festgelegt.
Durch die integration der DBMS-nativen Treiberanteile in den JDBC-Treiber muß dieser
für jedes anzusprechende DBMS neu erstellt werden. Eine Wiederverwendung der JDBC-spezifischen
Anteile, die für die Clientkommunikation eingesetzt werden, kann hierbei nicht erfolgen.
Der Fall der (partiellen) Konkretisierung dieser
Kommunikationsbeziehung zu einem beliebigen DBMS-neutralen
Protokoll wird durch einen Typ 3 Treiber
aufgegriffen.
Hier wird die DBMS-spezifische Komponente (in der
Abbildung grau dargestellt) als vom JDBC-Treiber
separiertes Modul aufgefaßt, daß mit diesem mittels eines
festgelegten neutralen Protokolls kommuniziert.
Durch diese Separierung, die auch durch Installation auf physisch getrennten
Maschinen --- der DBMS-spezifische Anteil könnte beispielsweise auf einem Middleware-Server untergebracht werden --- fundiert werden kann, gelingt die Wiederverwendung des JDBC-Treiberanteils, der
mit verschiedenen DBMS-spezifischen Bibliotheken über das gewählte Protokoll kommunizieren kann.
Der Typ 4 Treiber stellt die letzte durch
die JDBC-Spezifikation vorgesehene Ausprägung dar. Er
konzipiert eine vollständig in Java implementierte
Zugriffsschicht, die in sich geschlossen ist. Sie besitzt
daher lediglich die notwendige JDBC-Schnittstelle zur
Kommunikation mit der Java-Applikation und eine
DBMS-Spezifische zum Zugriff auf die Datenquelle.
Die Vorteile dieser Architekturvariante liegen in ihrer
Portabilität und den geringen Installations und
Wartungsaufwänden, die aus der Reduktion der
Kommunikationsbeziehungen resultieren. So kann ein solcher
Treiber durch einfache Integration in die Java-Applikation
verwendet werden und bedarf keiner Installationen oder
Modifikationen an der verwendeten Ausführungsumgebung.
Gleichzeitig offenbart sich diese Lösung jedoch als
technisch aufwendig in der Umsetzung, sobald
DBMS verschiedener Hersteller angesprochen werden sollen,
da die JDBC-Anteile des Treibers nicht separat
wiederverwendet werden können.
Hinsichtlich des Laufzeitverhaltens zeigt sich deutlich
die Schwäche der Typ 1 Treiber, welche in der
inhärent notwendigen Doppelkonversion (JDBC zu ODBC und
ODBC zu nativem Aufruf) begründet liegt. Daher sind
Treiber dieses Typs als Übergangserscheinung hin zu
„echten“ JDBC-Treibern, d.h. Treibern der
restlichen Typen, anzusehen und sollten in
Produktivumgebungen nicht eingesetzt werden.
Die Vorteile der Typ 2 und 3 Treiber seitens der
Ausführungsgeschwindigkeit liegen in den nativen
Codeanteilen begründet, welche für das jeweilige verwendete
DBMS optimiert werden können.
Zwar spricht der leichte Installations- und
Adminstrationsaufwand eindeutig für Typ 4
Treiber, jedoch fallen diese in ihrer
Leistungsfähigkeit durch die ausschließliche Verwendung
der Programmiersprache Java teilweise deutlich hinter
Treiber des Typs 2 und 3, mit unter sogar hinter solche des Typs 1,
zurück. Sie verkörpern jedoch den aus konzeptioneller
Sicht zu bevorzugenden Ansatz hinsichtlich Portabilität
und Vergleichbarkeit der erzielten quantitativen Ergebnisse.
Typischerweise kommen im
produktiven Einsatz jedoch Treiber der Typen 2 und 4 zum Einsatz,
die entweder durch den Hersteller des DBMS mitgeliefert werden (Typ 2) oder
auf der Basis publizierter Schnittstellen plattformunabhängig
für genau ein spezifisches DBMS entwickelt wurden (Typ 4).
Generell formuliert das JDBC-Konzept auf dieser Ebene noch keine Einschränkung hinsichtlich der unterstützten DBMS-Typen und ist generell auf verschiedenste Datenquellen anwendbar. Durch die Struktur des API und die verfügbaren Treiber kristallisieren sich jedoch relationale DBMS als Hauptanwendungsgebiet dieser Zugriffsschnittstelle heraus.
Im folgenden wird die Verwendung des Typ 4 Treibers Connector/J im Zusammenspiel mit dem RDBMS MySQL betrachtet.
Die Beispiele basieren auf einer Demodatenbank, deren Struktur und Inhalte nachfolgend angegeben sind.
+----------+-------+---------+-----------+------------+--------------------------+------+----------+-----------+------+
| FNAME | MINIT | LNAME | SSN | BDATE | ADDRESS | SEX | SALARY | SUPERSSN | DNO |
+----------+-------+---------+-----------+------------+--------------------------+------+----------+-----------+------+
| John | B | Smith | 123456789 | 1965-01-09 | 731 Fondren, Houston, TX | M | 30000.00 | 333445555 | 5 |
| Franklin | T | Wong | 333445555 | 1955-12-08 | 638 Voss, Houston, TX | M | 40000.00 | 888665555 | 5 |
| Joyce | A | English | 453453453 | 1972-07-31 | 5631 Rice, Houston, TX | F | 25000.00 | 333445555 | 5 |
| Ramesh | K | Narayan | 666884444 | 1962-09-15 | 975 Fire Oak, Humble, TX | M | 38000.00 | 333445555 | 5 |
| James | E | Borg | 888665555 | 1937-11-10 | 450 Stone, Houston, TX | M | 55000.00 | NULL | 1 |
| Jennifer | S | Wallace | 987654321 | 1941-06-20 | 291 Berry, Bellaire, TX | F | 43000.00 | 888665555 | 4 |
| Ahmad | V | Jabbar | 987987987 | 1969-03-29 | 980 Dallas, Houston, TX | M | 25000.00 | 987654321 | 4 |
| Alicia | J | Zelaya | 999887777 | 1968-07-19 | 3321 Castle, Spring, TX | F | 25000.00 | 987654321 | 4 |
+----------+-------+---------+-----------+------------+--------------------------+------+----------+-----------+------+
Das Klassendiagramm der Abbildung 2 zeigt die zentralen Klassen des Paketes
java.sql
.
Auffallend ist, daß alle Elemente des dargestellten Pakets
-- abgesehen von den definierten Exceptionklassen -- als
Schnittstellen ausgelegt sind. Durch diese Mimik wird die
Organisation der JDBC-Schnittstelle deutlich. Die API legt
lediglich das Verhalten hinsichtlich seiner Semantik und
die Einzeloperationen durch Definition ihrer Parameter
fest, die konkrete DBMS-spezifische Implementierung dieser
Operationen wird durch den JDBC-Treiber bereitgestellt.
Zentrale Klasse der JDBC-API ist die Schnittstelle Connection
. Sie bildet die
Kommunikationsverbindungen zum DBMS ab und bietet
notwendige Verwaltungsoperationen.
Hierunter fallen insbesondere auch die Aufrufe zur Transaktionssteuerung.
Die Schnittstelle Statement
realisiert genau eine aus
Javasicht atomare Datenbankaktion. Diese muß hierbei aus
minimal einem Aufruf an das DBMS bestehen, kann aber
eine Reihe separater Aufrufe zu einem Batch
bündeln.
Als Sonderform sieht die API die Spezialisierung PreparedStatement
vor, die es gestattet, parametrisierte Anfragen zwischenzuspeichern, die nach Belegung der Parameterfelder an das DBMS übergeben
werden. Hierdurch wird ein einfacher Mechanismus zur Wiederverwendung von DBMS-Aufrufen etabliert.
Liefert eine DBMS-Anfrage Ergebnistupel, so werden
diese konform zur Schnittstelle ResultSet
verwaltet. Diese Schnittstelle
erlaubt die lesende Traversierung der vom DBMS gelieferten
Tupel ebenso wie ihre Aktualisierung im Hauptspeicher und
das anschließende Zurückschreiben in die Datenbank.
Die in der Abbildung nur durch getXXX und updateXXX angedeuteten
Operationen existieren in Ausprägungen für alle unterstützten Datentypen, wobei XXX den Namen
des Typs bezeichnet.
Ferner definiert die API mit SQLWarning
eine Ausnahme zur Behandlung
auftretender Fehlersituationen sowie eine Reihe weiterer,
in der Abbildung 2 nicht
dargestellter Klassen wie beispielsweise verschiedene
Datentypen.
Die Klasse SQLException
bietet durch ihre Methoden getErrorCode
und getSQLState
Möglichkeiten an um die nähere Ursache eines datenbankseitigen Fehlers zu ermitteln.
Zusätzlich gestatten Objekte dieses Ausnahmetyps die Verschachtelung von Ausnahmen, d.h. die rekursive Einbettung eines Ausnahmeereignisobjekts in ein bestehendes. Auf diesem Wege können aufgetretene Fehler durch mehrere Ausnahmeobjekte näher spezifiziert werden.
Beispiel 1 zeigt die Abfrage von Details der empfangenen und aller eingebetteten Ausnahmeereignisobjekte mittels der durch die JDBC-API vorgesehenen Methoden.
Beispiel 1: Ermittlung von Fehlerdetails | |
Download des Beispiels |
Mit der Version 1.4 der Java-Standard-Edition wurde die zuvor nur in der JDBC-API zur Verfügung stehende Möglichkeit zur Schachtelung von Ausnahmeereignissen auch für beliebige Ausnahmeereignisobjekte des Typs Throwable
definiert.
Anders als die JDBC-API sieht die generische Lösung jedoch die Nutzung der Methode getCause
zur Extraktion der eingebetteten Ausnahmeereignisobjekte vor.
Der Code des Beispiels 2 spiegelt daher die Standard-API-konforme Realisierung wieder. Zusätzlich wendet die Lösung die Standard-Methode getMessage
zur Ermittlung der deskriptiven Fehlerbeschreibung an.
Beispiel 2: Standard-API-konforme Ermittlung von Fehlerdetails | |
Download des Beispiels |
Beispiel 3
zeigt den Ablauf zur Aufnahme einer Verbindung mit der
Datenbank jdbctest
auf dem lokalen Rechner
(localhost
).
Zunächst muß die Klasse des gewählten JDBC-Treibers (im
Beispiel com.mysql.jdbc.Driver
vor ihrer Verwendung
geladen werden. Dies geschieht durch den Aufruf der statischen Methode forName
auf der Klasse Class
.
Der zu ladende Treiber muß hierbei die
JDBC-Schnittstellenklasse Driver
implementieren um später durch
die JDBC-API verwendet werden zu können.
Gleichzeitig mit dem dynamischen Ladevorgang erfolgt die Registrierung des
Treibers beim JDBC-DriverManager
,
der die Verwaltung der geladenen DB-Treiber übernimmt.
Nach dem erfolgreichen Laden des Treibers wird durch den Aufruf von getConnection
(Zeile
16) die Verbindung zur Datenbank hergestellt. Die
anzusprechende Datenbank wird hierbei durch eine URI der
Form
jdbc:mysql://DB-Server/DB-Name
repräsentiert (Zeile 17). Zusätzlich können ein zur
Anmeldung am DB-System benötiger Benutzer (Zeile 18) und sein Paßwort (Zeile 19)
übergeben werden.
Beispiel 3: Aufbau einer Datenbankverbindung | |
Download des Beispiels |
Zusätzlich stellen die Klassen Driver
und
DriverManager
die Möglichkeit der Abfrage von
verbindungsunabhängigen Verwaltungsinformationen zur
Verfügung.
Beispiel 4: Ermittlung von Informationen über Treiber und Treibermanager | |
Download des Beispiels Download der Ergebnisdatei |
Beispiel 4
zeigt die Ermittlung des durch den
DriverManager
für alle durch ihn verwalteten
Treiber global definierten Login Timouts, der angibt
wie lange beim Anmeldevorgang an der Datenbank auf eine
Rückmeldung gewartet wird.
Zusätzlich werden für alle verwalteten Treiber
der Klassenname sowie Daten zur Version und zum Stand der
JDBC-Unterstützung ermittelt und ausgegeben.
Der JDBC-Unterstützungsstand gibt an, ob ein gegebener
Treiber die Konformitätstests der Firma SUN bestanden hat.
Voraussetzung hierfür ist u.a. die vollständige
Unterstützung des SQL 92-Standards (entry level).
Diese Interpreatation von Spezifikationskonformität
verwundert etwas, da alle JDBC-Treiber mit Ausnahme der
inhärent DB-neutralen Typ 1
Treiber DBMS-spezifisch realisiert sind. Aus
diesem Grunde bewertet der Konformitätstest vielmehr den
Umsetzungsgrad des SQL-Standards in dem via JDBC genutzten
DBMS als die Güte des JDBC-Treibers selbst.
Seit der JDBC-Schnittstellenversion 2 ist neben der „klassischen“ Zugriffsvariante auch eine auf dem Java Naming and Directory Interface (JNDI) basierende Zugriffsmethodik definiert, deren Verwendung --- abgesehen von der geänderten Mimik im Aufbau der DB-Verbindung --- identisch gestaltet ist.
Jedoch ist, wie in JNDI üblich, vor dem Zugriff ein
benanntes Objekt beim JNDI-Dienst zu registrieren.
Im Falle von JDBC ist dies ein Objekt welches die
Schnittstelle DataSource
implementiert.
Der Code des Beispiels 5 zeigt die notwendigen Schritte zur
Registrierung eines MysqlDataSource
-Objekts, der durch den MySQL-JDBC-Treiber gelieferten
Implementierung der Schnittstelle
DataSource
.
Beispiel 5: Ablage von Verbindungsinformation in einem JNDI-Verzeichnis | |
Download des Beispiels |
Entsprechend der modifizierten Ablage der Verwaltungsinformation
ändert sich die Erzeugung der
Datenbankverbindung beim Zugriff. Hier wird nun
zunächst über einen Zugriff auf den JNDI-Verzeichnisdienst
das benannte DataSource
-Objekt (es trägt den
Namen jdbc/mySrc
ermittelt.
Anschließend wird durch das dem Verzeichnisdienst
entnommene DataSource
-Objekt die
Datenbankverbindung (d.h. das
Connection
-Objekt) erzeugt.
Alle weiteren Schritte zur Interaktion mit der Datenbank
verlaufen dann identisch zur im Beispiel 3 gezeigten
Verbindungsaufnahme.
Der Code des Beispiels 6
zeigt die notwendigen Schritte zur Ermittlung der Referenz auf
das Objekt des Typs DataSource
aus dem JNDI-Verzeichnis,
sowie die Erzeugung des Connection
-Objekts.
Beispiel 6: Verbindungsaufbau unter Nutzung von JNDI | |
Download des Beispiels |
Auffallend ist die Ablage des Datenbanknamens im
Verzeichnisdienst mittels des Methodenaufrufs
setDatabaseName
. Diese Verschiebung der
Information wird durch die geänderte Mimik der
Erzeugung des Connection
-Objekts impliziert.
So sieht die Implementierung dieser Methode für die Klasse
DataSource
keine Möglichkeit zur
gleichzeitigen Übergabe von Anmeldenamen, Paßwort und
Datenbank vor.
Vielmehrnoch ist es sogar möglich diese Daten allesamt
innerhalb des JNDI-Verzeichnisdienstes abzulegen. (Für diesen Zweck
stehen die Methoden setUser
bzw. setPassword
zur Verfügung.)
Als Konsequenz hiervon kann der Verbinungswunsch durch
Aufruf der Methode getConnection
ohne weitere
Parameter erfüllt werden.
Diese Umsetzungsweise ist vor ihrer Realisierung
hinsichtlich des damit eintretenden Verlustes an
Sicherheit zu prüfen, da in ihrer Folge eine
Datenbankverbindung allein durch Kenntnis des
JNDI-residenten Namens des
DataSource
-Objektes erfolgen kann.
Generell wählen JDBC-Umsetzungen den Weg, jede Ausprägung eines Connection
-Objekts
in eine physische Datenbankverbindung abzubilden. Dieses, durchaus der intuitiven Semantik der
Connection
-Klasse entsprechende Vorgehen kann jedoch in realen Applikationen, begründet
in der Vielzahl der durch das DBMS zu verwaltenden Verbindungen, zu Zugriffsengpässen führen.
Aus diesem Grunde definiert die JDBC-Schnittstelle Operationen zur Zusammenfassung „gleichartiger“
Zugriffe. Hierzu zählen Zugriffe die unter derselben Nutzerkennung auf dieselbe Datenbank abgewickelt werden.
Diese Zugriffsform tritt insbesondere bei Anwendungen auf, die über nur einen in der Datenbank eingetragenen
Anwender verfügen und die gesamte Nutzerverwaltung datenbanktransparent applikationsseitig abwickeln.
Zur Optimierung von Zugriffen dieser Natur sieht die JDBC-Schnittstelle das sog. Connection Pooling vor,
welches gleichartige Zugriffe bündelt.
Das Beispiel 7 zeigt eine Umsetzung:
Beispiel 7: Verbindungsaufbau unter Nutzung von Connection Pooling | |
Download des Beispiels |
Statt für jede gewünschte Datenbankverbindung ein zusätzliches Objekt des Type Connection
zu erzeugen, wird die erzeugte Verbindung zur Konstruktion eines Objektes, welches Konform zur Schnittstelle
PooledConnection
definiert ist,
verwendet. Dieses sorgt für die Verwaltung der DB-Verbindung und stellt dieselbe physische
Verbindung verschiedenen Anfragern zur Verfügung.
Konsequenterweise wird daher eine neue Verbindung nicht mehr vom DriverManager
angefordert, sondern
durch die Methode getConnection
der aus der Verwaltungsstruktur entnommenen PooledConnection
beantragt.
Aufgrund der Unterstützung des SQL-Sprachumfanges, durch unveränderte textuelle Propagation an das DBMS sind durch JDBC im Allgemeinen alle Facetten der Datenbanksprache nutzbar, sofern sie durch das verwendete DBMS Unterstützung finden. Hierunter fallen:
JDBC reflektiert jedoch nicht diese Sprach(-sub-)klassen selbst in der API, sondern sieht vielmehr ausschließlich zwei Formen des Zugriffs vor. Solche die tabellenwerte Resultate liefern und solche, deren Ausführung lediglich primitivwertige Rückgabewerte liefert.
Primitivwertige Datenbankzugriffe liefern, abgesehen von
Fehler- oder Warnmeldungen, lediglich die Anzahl der
geänderten Tupel, falls zutreffend, oder 0 zurück.
Aus dieser Festlegung lassen sich diejenigen
SQL-Anweisungstypen ableiten, welche als primitivwertiger
Zugriff realisiert sind. Hierunter fallen alle Operationen
der Datendefinition wie CREATE
oder
ALTER TABLE
sowie alle Einfüge-
(INSERT
) Änderungs- (UPDATE
) und
Löschvorgänge (DELETE
). Darüberhinaus alle
Operationen zur Administration der Datenbank durch
Rechtevergabe (GRANT
, REVOKE
).
Zugriffe dieser Art werden generell durch die Methode
executeUpdate
, oder einer Abart davon,
realisiert.
Beispiel 8: Erstellung einer neuen Tabelle | |
Download des Beispiels Download der Ergebnisdatei |
Beispiel 8
zeigt die notwendigen Schritte zur Erstellung der Tabelle EMPLOYEE
in
der Datenbank.
Nach dem (üblichen) Verbindungsaufbau (Zeile 8-24) wird
in Zeile 27 eine Variable des Typs Statement
deklariert. Auch bei
Statement
handelt es sich um eine durch die
JDBC-API vordefinierte Schnittstelle, die als Bestandteil
des JDBC-Treibers von einer Klasse implementiert
wird.
Ausgehend von der etablierten Datenbankverbindung wird
durch Aufruf der Methode createStatement
eine konkrete Ausprägung konform zur
Statement
-Schnittstelle erzeugt (Zeile
29).
Der Aufruf von executeUpdate
übergibt das als
Zeichenkette abgelegte SQL-Kommando an die Datenbank zur
Ausführung.
Da durch CREATE TABLE
keine Tupeländerungen vorgenommen werden ist das
Resultat des Aufrufs der Rückgabewert 0
.
Beispiel 9
zeigt mit dem ALTER TABLE
-Kommando eine
weitere Anwendung der
executeUpdate
-Methode.
Auch in diesem Falle wird als Resultat 0
geliefert, da die Definition des
Primärschlüssels keine Änderungen an den verwalteten Datensätzen vornimmt.
Beispiel 9: Modifikation der Tabellendefinition | |
Download des Beispiels Download der Ergebnisdatei |
Beispiel 10: Einfügen von Werten | |
Download des Beispiels Download der Ergebnisdatei |
Beispiel 10
zeigt den Einfügevorgang von acht Werten in die durch die
vorangegangenen Beispiele erzeugte Tabelle
EMPLOYEE
.
Jeder der Einfügevorgänge der Zeilen 36-43 führt im Rahmen einer separaten Datenbankkommunikation
sequentiell genau einen Einfügevorgang durch, was durch den Rückgabewert 1
dokumentiert wird.
Zwar ist dieses Verfahren praktikabel und erzielt die angestrebten Resultate, jedoch ist es unter Zeiteffizienzgesichtspunkten inadäquat, da sich Einfüge- und Kommunikationsvorgänge zahlenmäßig entsprechen.
Aus diesem Grunde bietet die Schnittstelle Statement
die Möglichkeit zur Bündelung
einzelner SQL-Aufrufe in einem sog. Batch an.
Beispiel 11 zeigt die entsprechende Umgestaltung des vorangegangenen Beispiels.
Beispiel 11: Einfügen von Werten mittels eines Batches | |
Download des Beispiels |
Statt der Einzelübergabe der SQL
INSERT
-Anweisungen werden diese nun (in Zeile
36-43) in in einem Batch gesammelt. Hierzu werden die
SQL-Zeichenketten durch den Aufruf addBatch
innerhalb des
Statement
-Objekts abgelegt und durch Aufruf
der Methode executeBatch
gesammelt an das DBMS
übergeben.
Statt der Einzelresultate wird durch diese Aufrufvariante
ein Array geliefert, das die Einzelrückgabewerte der als
Batch übergebenen Aufrufe versammelt.
Dies verdeutlicht nochmals das nachfolgende Beispiel.
In ihm wird zunächst mittels ALTER TABLE
eine
neue Tabellenspalte zur Aufnahme des Wochentages der
Geburt erstellt und anschließend durch SQL
UPDATE
-Anweisungen die benötigten Daten aus
dem vorhandenen Geburtsdatum ermittelt.
Auch dieses Beispiel bedient sich zur Performancebeschleunigung der Möglichkeiten des Batchaufrufes.
Beispiel 12: Aktualisieren von Tabellendefinitionen und Werten | |
Download des Beispiels Download der Ergebnisdatei |
Die Ausführung liefert als Resultat:
Statement No 0 changed 8 rows
Statement No 1 changed 0 rows
Statement No 2 changed 1 rows
Statement No 3 changed 0 rows
Statement No 4 changed 1 rows
Statement No 5 changed 1 rows
Statement No 6 changed 2 rows
Statement No 7 changed 3 rows
So werden durch den ALTER TABLE
-Aufruf
(Indexnummer 0) alle acht Tupel der Tabelle modifiziert,
während die nachfolgenden Aufrufe nur Teilmengen davon
verändern.
Die nähere Betrachtung der Zeilen 37-43 des Quellcodes
von Beispiel 12
zeigt sich, daß diese im Kern denselben Vorgang ausführen,
nur jeweils mit variierenden Parametern.
Zur Behandlung von Fällen dieser Problemstellung definiert
die JDBC-API die Schnittstelle PreparedStatement
als Spezialisierung
von Statement
.
Diese Schnittstelle gestattet es, Anweisungen, die später
an die Datenbank übermittelt werden sollen, mit
Platzhaltern zu versehen und diese vor der Übermittlung
mit Werten zu befüllen.
Beispiel 13 zeigt die entprechende
Modifikation des vorangegangenen Beispiels.
Beispiel 13: Aktualisieren von Tabellendefinitionen und Werten | |
Download des Beispiels Download der Ergebnisdatei |
Im Beispiel wird neben dem Objekt des Typs Statement
zusätzlich eines des Typs PreparedStatement
erzeugt (Zeile 32).
Die dem Konstruktor übergebene Anweisung enthält als Sonderzeichen zur Markierung der Platzhalter
das Fragezeichen (?
).
Die Wochentage werde in Zeile 40, des vereinfachten Zugriffs wegen, als Array definiert.
In den Zeilen 42 mit 46 werden die benötigten SQL-UPDATE
-Anweisungen dynamisch
durch Einsetzen der geeigneten Werte in den vorpräparierten Änderungsausruck erzeugt und einem eigenen
Batch zugeordnet. Der Einsetzungsvorgang der benötigten Werte geschieht durch die Methoden
setString
für zeichenkettenartige bzw. setInt
für den
ganzzahlige Parameter. Den Methoden wird jeweils die Position des Parameters, gezählt ab 1 sowie die zu wählende Wertbelegung übermittelt.
Zur Ausführung müssen beide Batches getrennt angefordert werden.
Die in der Praxis quantitativ bedeutendste Klasse von
Datenbankzugriffen dürfte zweifellos auf die lesende
Ermittlung von bestehenden Daten darstellen, kurzum alle Spielarten der SQL SELECT
-Anweisung.
Für Anfragen an die Datenbank steht prinzipiell der gesamte durch das DBMS unterstützte SQL-Umfang zur Verfügung.
Anfragen werden im Gegensatz zu den bisher
betrachteten lesenden Zugriffen nicht als primivwerte
Methoden realisiert, sondern liefern als Resultat immer
eine Tabelle zurück.
Diese wird durch den API-Typ ResultSet
dargestellt.
Zusätzlich werden Anfragen durch die Methode executeQuery
ausgeführt.
Das Beispiel 14
zeigt die generische Extraktion von DB-Daten und den Zugriff auf
Metadaten.
Die aus der Datenbank gelesenen Ergebnistupel werden im durch rs
benannten ResultSet
abgelegt (Zeile 39). Die Resultatmenge wird
mithilfe eines Cursors (Datensatzzeiger) traversiert. Hierzu wird der initial auf
eine Ausgangsstellung vor dem ersten empfangenen Tupel positionierte Cursor durch Aufruf der Methode
next
solange weitergerückt, bis der letzte Datensatz verarbeitet wurde.
Der Aufruf der MethodegetMetaData
liefert deskriptive Metadaten wie Spaltenzahl sowie deren Bezeichner und Typen für die erstellte Resultattupelmenge.
In Zeile 43 werden diese Metadaten verwendet um die
Spaltennamen der extrahierten Attribute anzuzeigen.
Zeile 47-52 liest die einzelnen Werte jedes Tupels mittels
getObject
aus und stellt sie am
Bildschirm dar.
Beispiel 14: Auslesen von Daten und Metadaten | |
Download des Beispiels Download der Ergebnisdatei |
Neben im Beispiel 14
gezeigten Verarbeitung in exakter der Ablagereihenfolge der Datenbank
kann auch durch Definition eines Cursors die Traversierung in
inverser Ablagerichtung erreicht werden.
Das nachfolgende Beispiel illustriert das entsprechende Vorgehen durch anfängliche Positionierung
des Cursors ans Ende der empfangenen Daten (d.h. nach dem letzten Datensatz) und
anschließendes schrittweises Rückpositionieren durch Aufruf der Methode previous
.
Beispiel 15: Auslesen von Daten in invertierter Reihenfolge | |
Download des Beispiels Download der Ergebnisdatei |
Ferner kann der Cursor wahlfrei auf eine beliebige Position der Ergebnisrelation gesetzt werden.
Das nachfolgende Beispiel zeigt dies. Ferner illustriert es das Vorgehen zur Größenermittlung
des resultierenden ResultSet
s durch das Aufrufpaar last
und getRow
, welches
zunächst den Cursor auf den letzten aus der Datenbank extrahierten Datensatz positioniert und anschließend dessen
Nummer liefert.
Beispiel 16: Auslesen von Daten in wahlfreier Reihenfolge | |
Download des Beispiels Download der Ergebnisdatei |
Wird der benötigte ResultSet
geeignet
(d.h. mit den Parameter CONCUR_UPDATABLE
)
(siehe Zeile 49) initialisiert, so können Änderungen, die im
Hauptspeicher durch die JDBC-API durchgeführt werden, in
die Datenbank persistiert werden.
Beispiel 17 zeigt dies exemplarisch für den
Einfügevorgang eines neuen Tupels.
Die Voraussetzungen für Einfüge- und Aktualisierungsvorgänge entstprechen denen von updatable views, d.h. die Daten dürfen nur aus genau einer Tabelle entnommen sein und müssen den Primärschlüssel enthalten.
Beispiel 17: Auslesen und Einfügen von Daten | |
Download des Beispiels Download der Ergebnisdatei |
Auf dieselbe Weise können auch Tupel einer Relation
verändert werden. Hierzu stehen eine Reihe von
updateXXX
-Methoden zur Verfügung, wobei
XXX
für den Typ des zu aktualisierenden
Attributs steht.
Nach durchgeführter Modifikation der
hauptspeicherresidenten Werte werden diese durch updateRow
in die Datenbank
rückgeschrieben.
Beispiel 18 zeigt dies:
Beispiel 18: Modifizieren von Daten | |
Download des Beispiels |
Analog vollzieht sich der Löschvorgang mittels deleteRow
:
Beispiel 19: Löschen von Daten | |
Download des Beispiels |
Die bisher betrachteten Varianten extrahieren Daten aus der Datenbank im Stile einer Momentaufnahme
(snapshot) zum Zeitpunkt der Anfrage. Die einmal angefragten Inhalte können sich jedoch noch zur
Laufzeit der zugreifenden JDBC-Applikation datenbankseitig ändern, wenn sie durch eine andere Applikation
neu geschrieben werden. Zur Gewährleistung der Konsistenz des extrahierten Snapshots mit den tatsächlichen
Datenbankinhalten steht die Operation rowUpdated
zur Verfügung. Sie ermittelt ob der im Hauptspeicher befindliche Wert mit dem aktuellen Datenbankinhalt
übereinstimmt, d.h. ob der DB-Inhalt aktualisiert wurde.
Beispiel 20 zeigt ein Umsetzungsbeispiel.
Beispiel 20: Test auf geänderte Daten | |
Download des Beispiels |
Die Abbildung zeigt die Ergebnisse einiger Geschwindigkeitsmessungen als Vergleich zwischen dem Zugriff auf eine MySQL-Datenbank unter Nutzung der Textschnittstelle und der Abwicklung derselben Zugriffe mittels JDBC.
Zur Messung wurde eine nicht-indexierte Datenbank mit 107
Einträgen verwendet die aus der Relation tab
bestand. Deren Tupel wurden
aus Paaren von 36-Byte großen UUIDs gemäß dem Spezifikationsentwurf
der IETF gebildet.
Zur Zeitmessung wurden folgende Einzeloperationen betrachtet:
INSERT INTO tab
VALUES(...)
SELECT COUNT(*) FROM
tab
SELECT * FROM
tab
UPDATE tab SET UUID1="X" WHERE
UUID1<>"X"
X
enthalten.DELETE FROM tab WHERE
UUID2<>"X"
X
enthalten.Insgesamt zeigt sich ein ausgewogenes Bild, in welchem
der JDBC-Zugriff lediglich bei datenintensiven Zugriffen
(große Mengen schreibender Zugriffe bei INSERT
bzw. große Mengen lesender Operationen bei
SELECT
) im Bereich von fünf Prozent
zurückliegt.
Diese enge Vergleichbarkeit der beiden Zugriffsmodi rührt von den Realisierung des eingesetzten JDBC-Treibers her; insbesondere von der Handhabung der physischen Datenbankverbindung auf Ebene des Netzwerkprotokolls.
Die JDBC-API unterstützt mit Zugriffsmethoden auf die Datentypen BLOB
, CLOB
,
ARRAY
, Object
und Ref
bereits eine Untermenge des SQL:1999-Standards.
So können, vorausgesetzt das durch JDBC angesprochene DBMS unterstützt dies, große unstrukturierte Binär- oder Textdaten
sowie einfache verschachtelte Tabellen, mithin NF2-Strukturen verwaltet werden.
Beispiel 21 zeigt den Zugriff
auf ein als eingebettete Tabelle realisiertes mengenwertiges Attribut.
Die Beispieldatenbank wurde hierfür wie folgt modifiziert:
alter table EMPLOYEE ADD CAR SET('53M91','521R4', 'LLO415', 'XNU457');
update EMPLOYEE set CAR='XNU457' where SSN=123456789;
update EMPLOYEE set CAR='XNU457,521R4' where SSN="999887777";
Beispiel 21: Zugriff auf ein mengenwertiges Attribut | |
Download des Beispiels |
Das Beispiel unterstreicht die Rolle der mengenwertigen Attribute als eingebettete Tabellen.
So erfolgt der Zugriff auf die Einzelwerte des Attributs CAR
identisch zur Ermittlung der
Resultatmenge der SQL-Anfrage mittels getResultSet
. Auch die Traversierung der einzelnen
CAR
-Elemente erfolgt äquivalent.
Die Aufnahme der large objects in ihrer Ausprägungsform als Character Large Objects (CLOB)
oder Binary Large Objects (BLOB) stellen eine der zentralen Erweiterungen des SQL:1999-Standards
gegenüber seinen Vorgängern dar.
Zwar ist die Ablage großer unstrukturierter Datenobjekte in relationalen Datenbanken konzeptionell durchaus diskussionswert,
jedoch in der Praxis oftmals, trotz der teilweise erheblichen Geschwindigkeitseinbußen im Zugriff (so benötigt die Ausführung
der Beispielapplikation mit einem 106 Byte großen Datenstrom 1,1 Sekunden, während dieselbe
Operation dateisystembasiert in 0,1 Sekunde abläuft), gewünscht.
Beispiel 22 zeigt die notwendigen Schritte zur Ablage und erneuten Auslese
eines aus einer Datei gewonnen Binärdatenstroms in der Datenbank.
Die Beispieldatenbank wurde hierfür um ein Attribut zur Aufnahme binärer Daten erweitert:ALTER TABLE EMPLOYEE ADD binData blob;
Beispiel 22: Verarbeitung unstrukturierter Binärdaten | |
Download des Beispiels |
Die Binärdaten können naturgemäß nicht direkt in die SQL-UPDATE
-Anweisung eingebunden werden,
sie werden daher einer mittels prepareStatement
vorerzeugten Anweisung durch Aufruf der Methode setBinaryStream
übergeben.
Zur Steuerung des transaktionalen Verhaltens einer JDBC-Anfrage bietet die Klasse Connection
verschiedene Methoden an:
getIsolationLevel
.TRANSACTION_NONE
: Keinerlei TransaktionsunterstützungTRANSACTION_READ_UNCOMMITTED
: Auch nicht durch commit
freigegebene
Daten werden gelesen. Es können daher dirty reads, Nicht-wiederholbare- und Phantomlesevorgänge
auftreten.TRANSACTION_READ_COMMITTED
: Nur durch commit
freigegebene
Daten werden gelesen. nichtwiederholbare- und Phantomlesevorgänge können jedoch auftreten.TRANSACTION_REPEATABLE_READ
: Innerhalb einer Transaktion können die verarbeiteten
Daten nicht durch eine andere Transaktion verändert werden. Das Auftreten von dirty reads und
nichtwiederholbaren Lesevorgängen ist daher ausgeschlossen, Phantomlesevorgänge sind jedoch weiterhin möglich.TRANSACTION_SERIALIZABLE
: Strikte Isolation aller Transaktionen, auf dieser
Stufe sind auch Phantomlesevorgänge ausgeschlossen.setTransactionIsolation
setAutoCommit
. Die Übergabe von true
bewirkt die Aktivierung des Modus bei
dem jede Einzelanweisung sofort persistent übernommen und für andere Transaktionen sichtbar wird.getAutoCommit
ermittelt werden.commit
.rollback
.rollback(Savepoint s)
.setSavepoint
.Beispiel 1 zeigt
die Nutzung des Transaktionskonzepts.
Zunächst wird die aktuelle Isolationsstufe ermittelt und geprüft ob
das angesprochene DBMS die höchste durch JDBC vorhergesehene Isolationsstufe unterstützt.
Nach Abschaltung der automatischen Änderungsübernahme (setAutoCommit(false)
) werden
zunächst zwei Tupel in die Tabelle EMPLOYEE
eingefügt, die jedoch nur innerhalb der
laufenden Transaktion sichtbar werden, für alle anderen Transaktionen innerhalb des DBMS bleiben die
neuen Werte (zunächst) unsichtbar.
Eine angenommene Fehlersituation führt zum Rücksetzen der Transaktion durch (rollback
).
Nach Abschluß des Programms wurden zwar die beiden ersten Werte lokal in die Datenbank übernommen, aber noch
innerhalb der laufenden Transaktion wieder daraus entfernt, weshalb sie zu keinem Zeitpunkt für andere
Datenbankbenutzer sichtbar waren.
Beispiel 23: Transaktionsverarbeitung | |
Download des Beispiels Download der Ergebnisdatei |
Neben dem Zurücksetzen einer vollständigen Transaktion bietet
die JDBC-API auch die Möglichkeit alle Schritte bis zu einem anwenderdefierten aus
der Datenbank zu entfernen.
Beispiel 24 zeigt dies unter Verwendung der
Methode setSavepoint
zur Definition eines Sicherungspunktes und
rollback(sp)
zum Zurücksetzen bis zu diesem Sicherungspunkt.
Beispiel 24: Transaktionsverarbeitung mit Sicherungspunkten | |
Download des Beispiels |
Der Netzwerkmitschnitt zeigt den TCP-Kommunikationsverlauf
der SQL-Anfrage SELECT * FROM EMPLOYEE;
.
Die vom Anfrager an den MySQL-Server übermittelten Datenanteile sind rot hervorgehoben. Zusätzlich
sind die für eine gleichwertige native Kommunikation anfallenden Daten berücksichtigt. Ihre Anteile
entsprechen den zusätzlich durch Fettdruck hervorgehobenen.
0000 4 00 00 00 \n 4 . 0 . 1 2 - s t a n d a r d - l o g 00 ' 00 00 00 N 4 V
0020 5 G D a 9 00 , \b 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 01 87 00 ÿ ÿ
0040 ÿ m a r i o 00 N W N J S ] L K 00 03 00 00 02 00 00 00 \t 00 00 00 02 j d b c
0060 t e s t 03 00 00 01 00 00 00 0f 00 00 00 03 S H O W V A R I A B L E S 01 00
0080 00 01 02 19 00 00 02 00 \r V a r i a b l e _ n a m e 03 1e 00 00 01 þ 03 01 00 1f
00a0 11 00 00 03 00 05 V a l u e 03 00 01 00 01 þ 03 01 00 1f 01 00 00 04 þ \f 00 00 05 \b b
00c0 a c k _ l o g 02 5 0 7 00 00 06 07 b a s e d i r . / o p t / r a i d
00e0 / m y s q l - s t a n d a r d - 4 . 0 . 1 2 - p c - l i n u x -
0100 i 6 8 6 / 18 00 00 07 11 b i n l o g _ c a c h e _ s i z e 05 3 2 7 6
0120 8 00 00 \b 17 b u l k _ i n s e r t _ b u f f e r _ s i z e 07 8 3
0140 8 8 6 0 8 15 00 00 \t \r c h a r a c t e r _ s e t 06 l a t i n 1 á 00
0160 00 \n 0e c h a r a c t e r _ s e t s Ñ l a t i n 1 b i g 5 c z
0180 e c h e u c _ k r g b 2 3 1 2 g b k l a t i n 1 _ d e
01a0 s j i s t i s 6 2 0 u j i s d e c 8 d o s g e r m a n
01c0 1 h p 8 k o i 8 _ r u l a t i n 2 s w e 7 u s a 7 c
01e0 p 1 2 5 1 d a n i s h h e b r e w w i n 1 2 5 1 e s t o
0200 n i a h u n g a r i a n k o i 8 _ u k r w i n 1 2 5 1 u k
0220 r g r e e k w i n 1 2 5 0 c r o a t c p 1 2 5 7 l a t
0240 i n 5 15 00 00 0b 11 c o n c u r r e n t _ i n s e r t 02 O N 12 00 00 \f
0260 0f c o n n e c t _ t i m e o u t 01 5 17 00 00 \r 15 c o n v e r t _ c
0280 h a r a c t e r _ s e t 00 < 00 00 0e 07 d a t a d i r 3 / o p t / r
02a0 a i d / m y s q l - s t a n d a r d - 4 . 0 . 1 2 - p c - l i n
02c0 u x - i 6 8 6 / d a t a / 13 00 00 0f 0f d e l a y _ k e y _ w r i t
02e0 e 02 O N 19 00 00 10 14 d e l a y e d _ i n s e r t _ l i m i t 03 1 0
0300 0 1b 00 00 11 16 d e l a y e d _ i n s e r t _ t i m e o u t 03 3 0 0
0320 18 00 00 12 12 d e l a y e d _ q u e u e _ s i z e 04 1 0 0 0 \n 00 00 13
0340 05 f l u s h 03 O F F \r 00 00 14 \n f l u s h _ t i m e 01 0 ! 00 00 15 11
0360 f t _ b o o l e a n _ s y n t a x 0e + - > < ( ) ~ * : " " & |
0380 12 00 00 16 0f f t _ m i n _ w o r d _ l e n 01 4 14 00 00 17 0f f t _ m a
03a0 x _ w o r d _ l e n 03 2 5 4 1c 00 00 18 18 f t _ m a x _ w o r d _ l
03c0 e n _ f o r _ s o r t 02 2 0 1c 00 00 19 10 f t _ s t o p w o r d _ f
03e0 i l e \n ( b u i l t - i n ) \f 00 00 1a \b h a v e _ b d b 02 N O 0f 00
0400 00 1b \n h a v e _ c r y p t 03 Y E S 10 00 00 1c 0b h a v e _ i n n o d
0420 b 03 Y E S 0e 00 00 1d \t h a v e _ i s a m 03 Y E S \r 00 00 1e \t h a v e
0440 _ r a i d 02 N O 16 00 00 1f \f h a v e _ s y m l i n k \b D I S A B L
0460 E D 10 00 00 \f h a v e _ o p e n s s l 02 N O 15 00 00 ! 10 h a v e _
0480 q u e r y _ c a c h e 03 Y E S 0b 00 00 " \t i n i t _ f i l e 00 ( 00
04a0 00 # 1f i n n o d b _ a d d i t i o n a l _ m e m _ p o o l _ s i
04c0 z e 07 2 0 9 7 1 5 2 ! 00 00 $ 17 i n n o d b _ b u f f e r _ p o o
04e0 l _ s i z e \b 1 6 7 7 7 2 1 6 - 00 00 % 15 i n n o d b _ d a t a _
0500 f i l e _ p a t h 16 i b d a t a 1 : 1 0 M : a u t o e x t e n d
0520 16 00 00 & 14 i n n o d b _ d a t a _ h o m e _ d i r 00 19 00 00 ' 16 i
0540 n n o d b _ f i l e _ i o _ t h r e a d s 01 4 18 00 00 ( 15 i n n o
0560 d b _ f o r c e _ r e c o v e r y 01 0 1c 00 00 ) 19 i n n o d b _ t
0580 h r e a d _ c o n c u r r e n c y 01 8 ! 00 00 * 1e i n n o d b _ f
05a0 l u s h _ l o g _ a t _ t r x _ c o m m i t 01 1 18 00 00 + 14 i n n
05c0 o d b _ f a s t _ s h u t d o w n 02 O N 15 00 00 , 13 i n n o d b _
05e0 f l u s h _ m e t h o d 00 1c 00 00 - 18 i n n o d b _ l o c k _ w a
0600 i t _ t i m e o u t 02 5 0 17 00 00 . 13 i n n o d b _ l o g _ a r c
0620 h _ d i r 02 . / 17 00 00 / 12 i n n o d b _ l o g _ a r c h i v e 03
0640 O F F 1f 00 00 0 16 i n n o d b _ l o g _ b u f f e r _ s i z e 07 8
0660 3 8 8 6 0 8 1d 00 00 1 14 i n n o d b _ l o g _ f i l e _ s i z e 07
0680 5 2 4 2 8 8 0 1c 00 00 2 19 i n n o d b _ l o g _ f i l e s _ i n _
06a0 g r o u p 01 2 1d 00 00 3 19 i n n o d b _ l o g _ g r o u p _ h o m
06c0 e _ d i r 02 . / 1d 00 00 4 1a i n n o d b _ m i r r o r e d _ l o g
06e0 _ g r o u p s 01 1 1a 00 00 5 13 i n t e r a c t i v e _ t i m e o u
0700 t 05 2 8 8 0 0 18 00 00 6 10 j o i n _ b u f f e r _ s i z e 06 1 3 1
0720 0 7 2 19 00 00 7 0f k e y _ b u f f e r _ s i z e \b 1 6 7 7 7 2 1 6
0740 L 00 00 8 \b l a n g u a g e B / o p t / r a i d / m y s q l - s t
0760 a n d a r d - 4 . 0 . 1 2 - p c - l i n u x - i 6 8 6 / s h a r
0780 e / m y s q l / e n g l i s h / 17 00 00 9 13 l a r g e _ f i l e s
07a0 _ s u p p o r t 02 O N 10 00 00 : \f l o c a l _ i n f i l e 02 O N 15
07c0 00 00 ; 10 l o c k e d _ i n _ m e m o r y 03 O F F \b 00 00 < 03 l o g
07e0 03 O F F 0f 00 00 = \n l o g _ u p d a t e 03 O F F 0b 00 00 > 07 l o g _
0800 b i n 02 O N 16 00 00 ? 11 l o g _ s l a v e _ u p d a t e s 03 O F F
0820 15 00 00 @ 10 l o g _ s l o w _ q u e r i e s 03 O F F 11 00 00 A \f l o
0840 g _ w a r n i n g s 03 O F F 13 00 00 B 0f l o n g _ q u e r y _ t i
0860 m e 02 1 0 19 00 00 C 14 l o w _ p r i o r i t y _ u p d a t e s 03 O
0880 F F 1b 00 00 D 16 l o w e r _ c a s e _ t a b l e _ n a m e s 03 O F
08a0 F 1b 00 00 E 12 m a x _ a l l o w e d _ p a c k e t 07 1 0 4 7 5 5 2
08c0 ! 00 00 F 15 m a x _ b i n l o g _ c a c h e _ s i z e \n 4 2 9 4 9
08e0 6 7 2 9 5 1b 00 00 G 0f m a x _ b i n l o g _ s i z e \n 1 0 7 3 7 4
0900 1 8 2 4 14 00 00 H 0f m a x _ c o n n e c t i o n s 03 1 0 0 16 00 00 I
0920 12 m a x _ c o n n e c t _ e r r o r s 02 1 0 17 00 00 J 13 m a x _ d
0940 e l a y e d _ t h r e a d s 02 2 0 1d 00 00 K 13 m a x _ h e a p _ t
0960 a b l e _ s i z e \b 1 6 7 7 7 2 1 6 19 00 00 L \r m a x _ j o i n _
0980 s i z e \n 4 2 9 4 9 6 7 2 9 5 15 00 00 M 0f m a x _ s o r t _ l e n
09a0 g t h 04 1 0 2 4 17 00 00 N 14 m a x _ u s e r _ c o n n e c t i o n
09c0 s 01 0 12 00 00 O 0e m a x _ t m p _ t a b l e s 02 3 2 00 00 P 14 m a
09e0 x _ w r i t e _ l o c k _ c o u n t \n 4 2 9 4 9 6 7 2 9 5 * 00 00
0a00 Q 1f m y i s a m _ m a x _ e x t r a _ s o r t _ f i l e _ s i z
0a20 e \t 2 6 8 4 3 5 4 5 6 % 00 00 R 19 m y i s a m _ m a x _ s o r t _
0a40 f i l e _ s i z e \n 2 1 4 7 4 8 3 6 4 7 1b 00 00 S 16 m y i s a m _
0a60 r e c o v e r _ o p t i o n s 03 O F F 00 00 T 17 m y i s a m _ s
0a80 o r t _ b u f f e r _ s i z e 07 8 3 8 8 6 0 8 17 00 00 U 11 n e t _
0aa0 b u f f e r _ l e n g t h 04 8 1 9 2 14 00 00 V 10 n e t _ r e a d _
0ac0 t i m e o u t 02 3 0 13 00 00 W 0f n e t _ r e t r y _ c o u n t 02 1
0ae0 0 15 00 00 X 11 n e t _ w r i t e _ t i m e o u t 02 6 0 \b 00 00 Y 03 n
0b00 e w 03 O F F 13 00 00 Z 10 o p e n _ f i l e s _ l i m i t 01 0 F 00 00
0b20 [ \b p i d _ f i l e < / o p t / r a i d / m y s q l - s t a n d
0b40 a r d - 4 . 0 . 1 2 - p c - l i n u x - i 6 8 6 / d a t a / l i
0b60 n u x . p i d 0b 00 00 \ \t l o g _ e r r o r 00 \n 00 00 ] 04 p o r t 04
0b80 3 3 0 6 14 00 00 ^ 10 p r o t o c o l _ v e r s i o n 02 1 0 18 00 00 _
0ba0 10 r e a d _ b u f f e r _ s i z e 06 1 3 1 0 7 2 1c 00 00 ` 14 r e a
0bc0 d _ r n d _ b u f f e r _ s i z e 06 2 6 2 1 4 4 14 00 00 a 11 r p l
0be0 _ r e c o v e r y _ r a n k 01 0 1a 00 00 b 11 q u e r y _ c a c h e
0c00 _ l i m i t 07 1 0 4 8 5 7 6 13 00 00 c 10 q u e r y _ c a c h e _ s
0c20 i z e 01 0 14 00 00 d 10 q u e r y _ c a c h e _ t y p e 02 O N \f 00 00
0c40 e \t s e r v e r _ i d 01 1 17 00 00 f 11 s l a v e _ n e t _ t i m e
0c60 o u t 04 3 6 0 0 19 00 00 g 15 s k i p _ e x t e r n a l _ l o c k i
0c80 n g 02 O N 14 00 00 h 0f s k i p _ n e t w o r k i n g 03 O F F 17 00 00
0ca0 i 12 s k i p _ s h o w _ d a t a b a s e 03 O F F 13 00 00 j 10 s l o
0cc0 w _ l a u n c h _ t i m e 01 2 17 00 00 k 06 s o c k e t 0f / t m p /
0ce0 m y s q l . s o c k 18 00 00 l 10 s o r t _ b u f f e r _ s i z e 06
0d00 5 2 4 2 8 0 0b 00 00 m \b s q l _ m o d e 01 0 0f 00 00 n 0b t a b l e _
0d20 c a c h e 02 6 4 12 00 00 o \n t a b l e _ t y p e 06 I N N O D B 14 00
0d40 00 p 11 t h r e a d _ c a c h e _ s i z e 01 0 14 00 00 q \f t h r e a
0d60 d _ s t a c k 06 1 9 6 6 0 8 1d 00 00 r \f t x _ i s o l a t i o n 0f
0d80 R E P E A T A B L E - R E A D 0e 00 00 s \b t i m e z o n e 04 C E S
0da0 T 18 00 00 t 0e t m p _ t a b l e _ s i z e \b 3 3 5 5 4 4 3 2 \r 00 00
0dc0 u 06 t m p d i r 05 / t m p / 1c 00 00 v 07 v e r s i o n 13 4 . 0 . 1
0de0 2 - s t a n d a r d - l o g 13 00 00 w \f w a i t _ t i m e o u t 05
0e00 2 8 8 0 0 01 00 00 x þ 11 00 00 00 03 S E T a u t o c o m m i t = 1 03
0e20 00 00 01 00 00 00 18 00 00 00 03 S E L E C T * F R O M E M P L O Y E
0e40 E ; 01 00 00 01 \n 19 00 00 02 \b E M P L O Y E E 05 F N A M E 03 \n 00 00 01 ý
0e60 03 01 00 00 19 00 00 03 \b E M P L O Y E E 05 M I N I T 03 01 00 00 01 þ 03 00 00
0e80 00 19 00 00 04 \b E M P L O Y E E 05 L N A M E 03 \n 00 00 01 ý 03 01 00 00 17 00
0ea0 00 05 \b E M P L O Y E E 03 S S N 03 \t 00 00 01 03 03 01 00 00 19 00 00 06 \b E M
0ec0 P L O Y E E 05 B D A T E 03 \n 00 00 01 \n 03 00 00 00 1b 00 00 07 \b E M P L O
0ee0 Y E E 07 A D D R E S S 03 1e 00 00 01 ý 03 00 00 00 17 00 00 \b \b E M P L O Y
0f00 E E 03 S E X 03 01 00 00 01 þ 03 00 01 00 1a 00 00 \t \b E M P L O Y E E 06 S A
0f20 L A R Y 03 07 00 00 01 05 03 00 02 1c 00 00 \n \b E M P L O Y E E \b S U P E
0f40 R S S N 03 \t 00 00 01 03 03 00 00 00 17 00 00 0b \b E M P L O Y E E 03 D N O 03
0f60 01 00 00 01 03 03 00 00 00 01 00 00 \f þ R 00 00 \r 04 J o h n 01 B 05 S m i t h \t
0f80 1 2 3 4 5 6 7 8 9 \n 1 9 6 5 - 0 1 - 0 9 18 7 3 1 F o n d r e n
0fa0 , H o u s t o n , T X 01 M \b 3 0 0 0 0 . 0 0 \t 3 3 3 4 4 5 5
0fc0 5 5 01 5 R 00 00 0e \b F r a n k l i n 01 T 04 W o n g \t 3 3 3 4 4 5 5
0fe0 5 5 \n 1 9 5 5 - 1 2 - 0 8 15 6 3 8 V o s s , H o u s t o n ,
1000 T X 01 M \b 4 0 0 0 0 . 0 0 \t 8 8 8 6 6 5 5 5 5 01 5 T 00 00 0f 06 A
1020 l i c i a 01 J 06 Z e l a y a \t 9 9 9 8 8 7 7 7 7 \n 1 9 6 8 - 0 7
1040 - 1 9 17 3 3 2 1 C a s t l e , S p r i n g , T X 01 F \b 2 5
1060 0 0 0 . 0 0 \t 9 8 7 6 5 4 3 2 1 01 4 W 00 00 10 \b J e n n i f e r 01
1080 S 07 W a l l a c e \t 9 8 7 6 5 4 3 2 1 \n 1 9 4 1 - 0 6 - 2 0 17 2
10a0 9 1 B e r r y , B e l l a i r e , T X 01 F \b 4 3 0 0 0 . 0
10c0 0 \t 8 8 8 6 6 5 5 5 5 01 4 V 00 00 11 06 R a m e s h 01 K 07 N a r a y
10e0 a n \t 6 6 6 8 8 4 4 4 4 \n 1 9 6 2 - 0 9 - 1 5 18 9 7 5 F i r e
1100 O a k , H u m b l e , T X 01 M \b 3 8 0 0 0 . 0 0 \t 3 3 3 4
1120 4 5 5 5 5 01 5 S 00 00 12 05 J o y c e 01 A 07 E n g l i s h \t 4 5 3 4
1140 5 3 4 5 3 \n 1 9 7 2 - 0 7 - 3 1 16 5 6 3 1 R i c e , H o u s
1160 t o n , T X 01 F \b 2 5 0 0 0 . 0 0 \t 3 3 3 4 4 5 5 5 5 01 5 S 00
1180 00 13 05 A h m a d 01 V 06 J a b b a r \t 9 8 7 9 8 7 9 8 7 \n 1 9 6 9
11a0 - 0 3 - 2 9 17 9 8 0 D a l l a s , H o u s t o n , T X 01 M
11c0 \b 2 5 0 0 0 . 0 0 \t 9 8 7 6 5 4 3 2 1 01 4 G 00 00 14 05 J a m e s 01
11e0 E 04 B o r g \t 8 8 8 6 6 5 5 5 5 \n 1 9 3 7 - 1 1 - 1 0 16 4 5 0
1200 S t o n e , H o u s t o n , T X 01 M \b 5 5 0 0 0 . 0 0 û 01 1
1220 01 00 00 15 þ
Die Analyse des Datenverkehrs zeigt, daß im Falle der JDBC-basierten Kommunikation
ein gegenüber der nativen Schnittstelle um 3529 Byte vergrößertes Datenaufkommen ausgetauscht wird.
Diese zusätzliche Datenmenge fällt jedoch nur einmal zum Zeitpunkt des JDBC-Verbindungsaufbaus statisch an.
(Vgl. Mitschnitt der mehrfachen Ausführung einer SQL-Anfrage innerhalb einer
bestehenden JDBC-Verbindung)
Zusätzlich offenbart Zeile 0x40 des Datenverkehrs die verschlüsselte Übermittlung des Paßwortes des
Anwenders mario. Allerdings werden die per Anfrage ermittelten Nutzdaten (ab Zeile 0xe40) unverschlüsselt
über die Netzwerkschnittstelle übertragen und stellen somit ein potentielles Angriffsziel dar.
Abhilfe hierfür kann die Tunnelung des Datenverkehrs, beispielsweise mittels
SSH,
durch eine sichere Verbindung bieten.
Neben den bereits aus anderen Veranstaltungen bekannten Servlets und den davon abgeleiteten Java Server Pages bildet die Technik der Enterprise Java Beans (EJB) einen weiteren zentralen Baustein der Java 2 Enterprise Plattform. Als serverseitige Komponenten kommt den EJBs heute große Bedeutung in der Realisierung komplexer Anwendungen, insbesondere durch Umsetzung der sog. „Business Logik“, d.h. den nicht-interaktiven fachlichen Anwendungsteilen, zu.
Der Begriff der Enterprise Java Bean stützt sich auf dem historisch älteren der Java Bean. Eine solche stellt eine abgeschlossene wiederverwendbare Softwarekomponente dar, die nach ihrer Erstellung über festgelegte Schnittstellen parametrisiert und manipuliert werden kann. Hierzu muß eine Bean eine festgelegte Interaktionsschnittstelle bieten, die durch die Java Bean Spezifikation definiert ist. Es handelt sich dabei um eine Reihe von Konventionen, der eine Bean gehorchen muß, jedoch um keine festgelegte API, die durch eine Komponente zu implementieren ist.
Der Begriff der Enterprise Java Bean greift diese inhaltliche Fundierung auf und präzisiert gleichzeitig die technische Umsetzung. So stellt eine Enterprise Java Bean eine Softwarekomponente dar, die in einer festgelegten Ausführungsumgebung, welche durch die EJB-Spezifikation festgelegte Dienste zur Nutzung durch die Beans anbieten kann. Eine solche Ausführungsumgebung wird als Container bezeichnet.
Ziel der Trennung in Komponente und Ausführungsumgebung ist die Zielsetzung, die Enterprise Java Bean ausschließlich zur Umsetzung fachlicher Aufgaben heranzuziehen und alle infrastrukturellen Fragestellungen wie Betriebsmittelverwaltung, Persistenz oder Sicherheit durch die Ausführungsumgebung in gleicher Weise für alle Komponenten bereitzustellen.
Ein EJB-Container wird zumeist im Rahmen eines Application-Servers bereitgestellt.
Die gelegentlich anzutreffende Hervorhebung der anfänglich für Java Beans intendierten visuellen Manipulationsmöglichkeit trifft für Enterprise Java Beans nicht zu und hat sich für Java Beans auch nur begrenzte Bedeutung erlangt.
Spezifikationsgemäß können EJB-Container folgende Eigenschaften offerieren:
Neben den in der Aufzählung dargestellten Eigenschaften dürfen Container zusätzlich Weitere wahlfrei implementieren.
Grundsätzlich lassen sich alle EJBs drei Typen zuordnen: Session Beans, Entity Beans und Message Driven Beans. Während erstere hauptsächlich zur Abbildung von Abläufen eingesetzt werden, dienen Entity Beans der Abwicklung von Zugriffen auf Daten. Eine Sonderstellung nehmen die Message Driven Beans ein, die lediglich hinsichtlich ihres Kommunikationsverhaltens festgelegt sind.
Session Beans dienen der Abbildung von Abläufen im Rahmen der Programmierung der sog. Business Logik. Die Lebensdauer (d.h. Zeitspanne zwischen Erzeugung im und Entfernung aus dem Hauptspeicher) ist daher identisch mit der einer durch den Client erfolgenden Anfrage. Jede zu einem Zeitpunkt existierende Session Bean repräsentiert daher eine zugehörige Clientinstanz.
Nach ihrer internen Ausgestaltung werden stateless und statefull Session Beans unterschieden. Während Erstere keinen über einen einzigen Aufruf hinausgehenden Zustand verwalten und daher seiteneffektfrei lediglich auf den durch den Aufruf übermittelten Daten operieren erhält das zustandserhaltende Pendant die Daten eines Aufrufs und kann diese auch in nachfolgenden Aufrufen verarbeiten.
Entity Beans sind programmiersprachliche Stellvertreter datenbankresidenter Objekte. Sie dienen dem erleichterten Zugriff auf persistent vorliegende Datenbestände. Ihre interne Realisierung ist eng mit der Technik relationaler Datenbankmanagement System verbunden. So werden Sie durch einen anwenderdefinierten Primärschlüssel dauerhaft identifiziert.
Message Driven Beans sind hinsichtlich ihres Kommunikationsverhaltens auf asynchrone Aufrufe beschränkt. Die Realisierung des eigentlichen Verhaltens wird durch eine Ausprägung eines der anderen Beantypen geboten.
Konzeptionell umfaßt jede EJB-Anwendung, die Session Beans einsetzt, die in Abbildung 4 dargestellten Teile:
Gegenüber der Realisierung als RMI-Anwendung benötigt die Umsetzung als zustandslose Session Bean die Erstellung einer sog. „Home-Schnittstelle“ (Home Interface), welche die Operation create
zur Instanziierung des serverseitigen EJB-Objekts bietet.
Sie ist im Beispiel 25 dargestellt.
Beispiel 25: Home-Schnittstelle einer EJB | |
Download des Beispiels |
Die anwenderdefinierte Home-Schnittstelle erweitert die durch die Standard-API vorgegebene Schnittstelle EJBHome
. Diese definiert Operationen zur Entfernung existierender EJB-Objekte aus dem Hauptspeicher (remove
) sowie zur Ermittlung von Metadaten (getEJBMetaData
) oder zum Erhalt eines netzwerkunabhängigen Verweises auf das EJB-Objekt (getHomeHandle
).
Im Einzelnen sind dies die Operationen:
EJBMetaData getEJBMetaDate()
EJBMetaData
-Objekt welches einzelne Eigenschaften einer EJB näher beschreibt. Hierzu zählen: HomeHandle getHomeHandle()
HomeHandle
zurück, welches eine netzwerkunabhängige Abstraktion des Verweises auf das Home-Objekt realisiert.void remove (Handle h)
Handle
) identifiziertes EJB-Objekt aus dem Hauptspeicher.void remove (Object pk)
Interessanterweise definiert die Schnittstelle zwar Operationen zur Ermittlung von Daten über bestehende Objekte und zur Entfernung dieser Objekte aus dem Hauptspeicher, nicht jedoch zu ihrer Erzeugung.
Dies liegt in der durch die Programmiersprache Java angestrebten statischen Typsicherheit begründet, die es nicht gestattet Operationen mit variablen Parameterlisten --- wie sie für die zum API-Erstellungszeitpunkt unbekannten spezifischen Initialisierungsparameter aller denkbaren EJBs benötigt würden --- zu versehen.
Aus diesem Grunde definiert die EJB-Spezifikation informell, daß ein diese Schnittstelle erweiternde eigene Home-Schnittstelle zusätzlich die Methode create
, deren Signatur als Rückgabetyp den Typ der Remote-Schnittstelle vorsehen muß definiert. Zusätzlich enthält diese Operation die zur Initialisierung der Bean benötigten Parameter in ihrer Parameterliste.
Die im Beispiel 26 ist Remote-Schnittstelle dargestellt, deren Ausprägungen von Home-Objekten angesprochenen werden:
Beispiel 26: Remote-Schnittstelle einer EJB | |
Download des Beispiels |
Schnittstellen dieses Typs enthalten ausschließlich die fachlichen Operationen, d.h. die Signaturen der Methoden, die später durch den Client benutzt werden.
Jede Remote-Schnittstelle erweitert zusätzlich die vorgegebene Schnittstelle EJBObject
, welche, ähnlich zur Home-Schnittstelle, einige Operationen zur Verwaltung eines EJB-Objektes vorgibt:
EJBHome getEJBHome()
Handle getHandle()
HomeHandle
zurück, welches eine netzwerkunabhängige Abstraktion des Verweises auf das Home-Objekt realisiert.Object getPrimaryKey()
boolean isIdentical(EJBObject eo)
void remove()
Beispiel 27 zeigt die Implementierung der Bean selbst:
Beispiel 27: Realisierung einer Session Bean | |
Download des Beispiels |
Die programmiersprachliche Umsetzung der Bean enthält die Methoden der in der Remote-Schnittstelle bekanntgegebenen fachlichen Operationen. Zusätzlich muß ein Konstruktur expliziert werden, dessen Parameterliste mit den für die Operation create
des Home-Interfaces gegebenen übereinstimmen.
Spezifikationsgemäß muß jede Session Bean die gleichnamige API-Schnittstelle implementieren. Diese definiert einige Operationen zur Behandlung unterschiedlicher Lebenszyklusstadien einer EJB. Hierunter fallen Methoden, die beim Erzeugen (ejbCreate
), Entfernen (ejbRemove
), bei der Passivierung (d.h. Auslagerung auf Hintergrundspeicher) (ejbPassivate
) und dessen Reaktivierung (ejbActivate
) eines EJB-Objekts durch die Ausführungsumgebung aufgerufen werden.
Bei der zwingend zu implementierenden Schnittstelle SessionBean
handelt es sich nicht nur um eine Konvention um die Umsetzung der Lebenszyklusschnittstelle sicherzustellen, sondern auch um die Kategorisierung der Bean selbst. So stellt die im Beispiel verwandte Schnittstelle SessionBean
neben EntityBean
und MessageDrivenBean
eine Spezialisierung der (operationslosen) Schnittstelle EnterpriseBean
dar, deren „Implementierung“ durch eine Klasse lediglich zur Kennzeichnung dieser als EJB herangezogen wird.
Die genannten Spezialisierungen dieser Schnittstelle erfüllen daher sowohl den Zweck der Ausübung des Implementierungszwanges für die in ihnen aufgeführten Operationen als auch den der typisierenden Kennzeichnung.
Darüberhinaus ist EnterpriseBean
als Spezialisierung der Standard-Schnittstelle Serializable
angelegt. In der Konsequenz muß jedes EJB-Objekt durch die Javasprachmechnismen serialisierbar sein. Diese Eigenschaft wird insbesondere für die Passivierung und im Rahmen der Entity Beans genutzt.
Konzeptionell erinnert die Trennung in publizierte fachliche Schnittstelle (Remote-Schnittstelle) und deren technischer Umsetzung durch die EJB an die aus der Betrachtung des Remote Method Invocation Mechanismus bekannte Struktur.
Allerdings weicht die Umsetzung der Bean von der dort anzutreffenden Konvention ab, die publizierte Schnittstelle selbst durch die realisierende Klasse zu implementieren. Dies liegt vor allem an der gegenüber RMI veränderten Struktur der publizierten Schnittstelle begründet. Während RMI für die Schnittstelle die Spezialisierung der operationslosen Standardschnittstelle Remote
fordert die EJB-Spezifikation die Erweiterung der Schnittstelle EJBObject
, welche selbst die oben dargestellten Operationen definiert. Aus diesem Grunde würde die Aufnahme der Remote-Schnittstelle, obwohl konzeptionell durchaus zu rechtfertigen, in die Umsetzungsliste der EJB gleichzeitig die Implementierung von zumindest leeren Methodenrümpfen für die in EJBObject
definierten Operationen notwendig werden lassen.
Abgesehen von dieser Ausnahme rekonstruiert das Verhältnis zwischen EJB und deren Remote-Schnittstelle die aus RMI bekannte Beziehung zwischen Schnittstelle und Umsetzung.
Die Nutzung einer durch eine Java Bean angebotenen Funktionalität erfolgt gemäß dem in Abbildung 4 dargestellten Schema. Ein dies umsetzender Client ist in Beispiel 28 dargestellt.
Beispiel 28: Zugriff auf eine Session Bean | |
Download des Beispiels |
Zunächst ermittelt der Client unter Nutzung der JNDI-API eine Referenz auf die EJB. Dies geschieht durch Anfrage (lookup) an den JNDI-Dienst unter Übergabe des bekannten Klarnamens (helloBean).
Die erhaltene generische Referenz wird durch Aufruf der statischen Methode narrow
der Klasse PortableRemoteObject
typsicher in eine Ausprägung der Home-Schnittstelle konvertiert.
Der Aufruf der in dieser Schnittstelle durch den Anwender definierten create
-Methode sorgt für die serverseitige Instanziierung der EJB, die als Ausprägung der Remote-Schnittstelle geliefert wird. Tatsächlich wird nicht das EJB-Objekt selbst durch den Methodenaufruf retourniert, sondern lediglich ein netzwerktransparenter Verweis darauf, der jedoch clientseitig einer lokalen Objektreferenz gleichgestellt verwendet werden kann.
Ferner wird serverseitig zur Kommunikation mit der EJB ein Home-Objekt erzeugt, welchem eine Stellvertreterrolle für den anfragenden Client zukommt.
Der Aufruf der durch die EJB zur Verfügung gestellten Methode erfolgt identisch zu dem einer Lokalen.
Die zweite zentrale Klasse von Enterprise Java Beans bilden die zur serverseitigen Persistierung von Objekten dienenden Entity Beans.
Sie kapseln Datenbankinhalte durch Objekte, die gemäß der EJB-Spezifikation instanziierbar und zugreifbar sind. Die Verwaltung der gekapselten Dateninhalte erfolgt durch ein frei festlegbares Datenbankmanagementsystem, die der Objekte selbst durch den EJB-Container.
Ziel dieser Technik ist es die Komplexität der Persistenzlogik für den Verwender der bereitgestellten Bean vollkommen transparent zu gestalten und serverseitig zu realisieren.
Die Familie der Entity Beans selbst zerfällt in zwei Untertypen, welche sich entlang des Realisierungspunktes der Persistenzlogik separieren: Bean Managed Persistence, der Bean obliegt die Umsetzung der Persistenzlogik, und Container Managed Persistence, hierbei wird die Persistenzlogik durch den EJB-Container realisiert.
Das nachfolgende Beispiel zeigt die Umsetzung einer Entity Bean mit Bean Managed Persistence. Es kapselt die Verwaltung und den Zugriff auf Objekte, die Personen beschreiben. Jedes Person
en-Objekt enthält Daten zu Name, Geburtsdatum und Wohnstraße. Der Name dient als eindeutige Identifikation und daher datenbankseitig als Primärschlüssel. Die notwendige Datenbanktabelle wurde erzeugt durch den SQL-Ausdruck: CREATE TABLE PERSON( Name VARCHAR(20) PRIMARY KEY, Birthdate DATE, Street VARCHAR(30));
Wie bereits für die Realisierung von Session Beans eingeführt, werden auch zur Publikation der extern zugänglichen Schnittstellen Home und Remote Interfaces benötigt.
Struktur und Aufbau der Home-Schnittstelle ähnelt konzeptionell der für Session Beans eingeführten. Dieser Schnittstellentyp dient auch für Entity Beans zur Aufnahme der Verwaltungsoperationen zur Erzeugung (create
) und zur Suche existierender EJBs (findByPrimaryKey
).
Beispiel 29 zeigt die Home-Schnittstelle des Beispiels.
Beispiel 29: Home-Schnittstelle einer Entity Bean | |
Download des Beispiels |
Die Home-Schnittstelle zeigt die create
-Operation zur Erzeugung einer neuen EJB-Instanz. Ihre Übergabeparameter dienen zur Konstruktion des neuen Objekts und werden durch die Bean-Implementierung interpretiert.
Ferner enthält die Schnittstelle mit findByPrimaryKey
eine Operation, deren Implementierung eine Entity-Bean anhand ihres Primärschlüssels identifiziert und liefert. Aus diesem Grunde erhält die Methode den zu suchenden Wert vom Typ des Primärschlüssels übergeben.
Hinsichtlich der verwendeten Typen zeigt sich bereits hier, daß eine Abbildung der durch die Programmiersprache Java bereitgestellten Typen auf die des eingesetzten Persistenzsystems stattfinden muß. In der im Beispiel gewählten Ausführungsform der durch die EJB selbst verwalteten Persistenz muß diese Abbildung manuell durch den Programmierer bereitgestellt werden.
Die Remote-Schnittstelle gibt die für Nutzer der Bean zugänglichen Geschäftsfunktionen wieder. Daher enthält dieser Schnittstellentyp lediglich Operationen zum Zugriff auf die verwalteten Daten, nicht jedoch zur technischen Verwaltung und Interaktion mit dem Bean-Container.
Per Konvention muß diese Schnittstelle als Spezialisierung der Schnittstelle EJBObject
definiert sein. Diese Standardschnittstelle definiert allgemeine Interaktionsformen, wie Löschen (remove
), Vergleich (isIdentical
) und Ermittlung des Primärschlüsselwertes (getPrimaryKey
) die für alle Entity Bean Objekte gleichermaßen benötigt werden.isIdentical
liefert den Vergleich zweier serverseitiger EJB-Objekte und ermittelt so, ob zwei Java-Objektreferenzen auf dasselbe Datenbankobjekt zugreifen.getPrimaryKey
ermittelt den Wert des Primärschlüssels eines gegebenen EJB-Objekts aus der Datenbank.
Zur Lösung von datenbankresidenten Objekten wird remove
eingesetzt. Der Aufruf dieser Methode entfernt ausschließlich die durch die EJB repräsentierten Datenbanktupel, die programmiersprachliche Objektrepräsentation bleibt jedoch über die gesamte Laufzeit (sofern nicht durch Gültigkeitsbereiche oder explizite NULL
-Setzung explizit anders gehandhabt) intakt.
Beispiel 30: Remote-Schnittstelle einer Entity Bean | |
Download des Beispiels |
Beispiel 31 zeigt den vollständigen Code der Bean. Sie implementiert mit EntityBean
die Standardschnittstelle aller Entity Beans, welche als Spezialisierung der ausschließlich markierenden Schnittstelle EnterpriseBean
die notwendigen Basisoperationen zur Abwicklung der persistenten Speicherung bieten.
Im Falle einer Bean Managed Persistence enthalten die Methoden der durch die Schnittstelle definierten und der zusätzlich im Rahmen der Spezifikation textuell definierten Operationen die notwendigen Aufrufe zur Ablage eines Objekts in der Datenbank und zu seiner späteren Extraktion daraus.
Im Einzelnen sind dies die Operationen:
Tabelle 1: Persistenzoperationen einer Entity Bean | |||||||||||||||
|
Beispiel 31: Eine Entity Bean | |
Download des Beispiels |
Zusätzlich enthält die Bean des Beispiels mit getAge
eine zwar in der Remote-Schnittstelle veröffentlichte Operation, die keinen direkt abgespeicherten Wert liefert, sondern diesen dynamisch zur Ausführungszeit anhand der verfügbaren Daten berechnet.
Alle anderen in der Remote-Schnittstelle aufgeführten Operationen (etwa: getStreet
, setStreet
) modifizieren lediglich, den durch die Attribute repräsentierten Java-Objektzustand und greifen nicht direkt auf die Datenbank zu.
Innerhalb der Datenbankzugreifenden Methoden muß durch den Anwender die Abbildung der Java-Datentypen auf die des verwendeten Datenbankmanagementsystems erfolgen. Die mit dem Präfix ejb
versehenden Methoden zeigen dies für die lesenden und schreibenden DB-Zugriffe. So kann die im Beispiel für name
und street
verwendete Java-Repräsentation String
vergleichsweise leicht in den SQL-Typ VARCHAR
abgebildet werden, sofern alle Methoden, die Datenbankinhalte schreiben, sicherstellen, daß nur zum Datenbankschema konforme Werte eingefügt werden. Die Beispielimplementierung zeigt dies examplarisch anhand der Methoden ejbCreate
und setStreet
.
Für programmiersprachliche Typen, die nicht direkt in DB-Typen abbildbar sind, muß im Falle der Bean Managed Persistence der Bean-Entwickler selbst Sorge für die adäquate Abbildung tragen. Das Beispiel illustriert dies anhand des Java-Datumstyps GregorianCalendar
, der manuell in die durch das DBMS erwartete ISO 8601-konforme Darstellung zu überführen ist.
Einige der möglichen Interaktionen mit der Bean zeigt der Code des Clients aus Beispiel 32:
Beispiel 32: Client der auf eine Entity Bean zugreift | |
Download des Beispiels |
Der Client ermittelt zunächst per JNDI eine Referenz auf die Bean, welche unter dem Namen personBean im Verzeichnisdienst registriert ist.
Die erhaltene generische Referenz wird durch Aufruf der statischen Methode narrow
der Klasse PortableRemoteObject
typsicher in eine Ausprägung der Home-Schnittstelle (PersonHome
) konvertiert.
Der Aufruf der in dieser Schnittstelle durch den Anwender definierten create
-Methode sorgt für die serverseitige Instanziierung der EJB, die als Ausprägung der Remote-Schnittstelle geliefert wird. Tatsächlich wird nicht das EJB-Objekt selbst durch den Methodenaufruf retourniert, sondern lediglich ein netzwerktransparenter Verweis darauf, der jedoch clientseitig einer lokalen Objektreferenz gleichgestellt verwendet werden kann.
Ferner wird serverseitig zur Kommunikation mit der EJB ein Home-Objekt erzeugt, welchem eine Stellvertreterrolle für den anfragenden Client zukommt.
Der Aufruf der durch die EJB zur Verfügung gestellten Methode erfolgt identisch zu dem einer Lokalen.
So dient der Aufruf der Methode create
zur Erzeugung von serverseitig instanziiert und transparent persistierten EJB-Objekten sowie den lokalen Java-(Stellvertreter-)Objekten für den Zugriff darauf.
Der Aufruf von getAge
zeigt die Nutzung einer in der Remote-Schnittstelle veröffentlichten Zugriffsmethode. Mit getPrimaryKey
wird die, in der durch die Remote-Schnittstelle erweiterten Schnittstelle EJBObject
angesiedelte, Operation zur Ermittlung des Primärschlüsselwertes eines EJB-Objektes aufgerufen.
Die Methode remove
stellt dagegen eine durch die Home-Schnittstelle definierte Operation dar. Durch den Aufruf dieser Methode auf dem durch p1
referenzierten Objekt wird durch Ausführung der Beanmethode ejbRemove
die die Bean serverseitig repräsentierenden Datenbankeinträge entfernt sowie der durch die Bean belegte Speicherbereich als frei markiert. Alle Versuche nach Aufruf dieser Methode auf der clientseitigen hauptspeicherrepräsentation Wertänderungen durchzuführen führen daher zu einem Fehler.
Die Ermittlung von Referenzen auf existierende EJB-Objekte erfolgt durch die in der Remote-Schnittstelle definierte Methode findByPrimaryKey
. Der EJB-Container stellt sicher, daß verschiedene Referenzen auf dasselbe EJB-Objekt synchronisiert in die Datenbank abgebildet werden, so daß keine Inkonsistenzen entstehen.
Für den Betrieb einer Enterprise Java Bean ist neben den bisher betrachteten Schnittstellen-Komponenten und der Realisierung der Bean selbst auch ein als Deployment Deskriptor bezeichnetes XML-Konfigurationsfile notwendig, welches verschiedene Einstellungsdaten sowie die Schnittstellendaten enthält.
Beispiel 33 zeigt ein Beispiel hierfür:
Beispiel 33: Deployment Deskriptor der Entity Bean | |
Download des Beispiels |
Hintergrund des Ansatzes der Java Data Objects (JDO) ist es, die bestehenden
Schnittstellenmechanismen dahingehend weiterzuentwickeln, daß die Persistenz von Objekten und Objektgraphen
für den Programmierer vollständig transparent durch Komponenten der Laufzeitumgebung zur Verfügung gestellt
werden.
Gleichzeitig etabliert JDO eine Abstraktion der verschiedenen Speicherungsmöglichkeiten und erlaubt es
beispielsweise die dateibasierte Ablage innerhalb des Programmes identisch zur Objektspeicherung
in einem Datenbankmanagementsystem zu handhaben. Auf dieser Basis läßt sich im Bedarfsfalle den
Persistenzdienstleister auszutauschen ohne Änderungen am Programmcode zu erfordern.
Plakativ wird der Ansatz daher, in Anlehnung an die Zielsetzung der Programmiersprache Java des
write once -- run anywhere, als write once -- store anywhere charakterisiert.
Um die weitestgehend transparente Handhabung der Objektpersistenz zu gewährleisten bedient sich JDO eines Ansatzes der über das alleinige Angebot einer Programmierschnittstelle hinausreicht. Die Zielsetzung der möglichst einfach handzuhabenden Interaktion mit den generischen Persistenzmechanismen läßt sich zwar durch das Angebot von durch den Programmierer zu implementierenden Schnittstellen und Persistenzklassen erreichen, jedoch ist der Einsatz signifikant komplexer als der bestehenden Persistenzschnittstellen. Darüberhinaus konterkariert der Zwang bei der Programmerstellung vorgegebene Schnittstellen zu berücksichtigen die Zielsetzung weitestgehender Transparenz der angebotenen Speichermechanismen.
Daher führt JDO die Technik der sog. Bytecodeanreicherung (engl. bytecode enhancing) ein.
Hierbei wird durch eine Programmkomponente vorübersetzer Bytecode so abgeändert, daß die notwendigen
Persistenzanweisungen in den bereits erzeugten ausführbaren Bytecode eingewoben werden.
Die benötigte Übersetzerkomponente wird durch die jeweilige JDO-Implementierung zur Verfügung gestellt und
muß durch den Programmierer im Bedarfsfalle lediglich geeignet parametrisiert werden.
Im Falle der Referenzimplementierung müssen daher alle Klassen, die Objekte ausprägen, welche
persistiert werden sollen, mit dem Werkzeug entsprechend nachbearbeitet werden. Der notwendige Aufruf
hat folgende Struktur:
java com.sun.jdori.enhancer.Main -d enhanced de/jeckle/jdotest/Employee.class de/jeckle/jdotest/Employee.jdo
.
Dieser Aufruf reichert die bereits übersetzte Klasse Emplyoee
innerhalb der Pakethierarchie
de.jeckle.jdotest
um Persistenzdaten an und legt das Ergebnis innerhalb des
Dateisystemkatalogs de/jeckle/jdotest
ab. Zur Anreicherung wird die Konfigurationsdatei
Employee.jdo
herangezogen, die im selben Pfad abgelegt ist wie die Quellcodedatei.
Alternativ zu diesem Ansatz steht auch die Möglichkeit zur Verfügung die benötigten Anweisungen bereits im Quellcode vorzusehen um so dasselbe Resultat zu erzielen, welches durch den Anreicherungsprozeß erzeugt wird. Diese Vorgehensweise hat jedoch wegen der damit verbundenen Aufwände kaum praktische Bedeutung erlangt und wird daher im folgenden nicht vertieft betrachtet.
Die Beispiele dieses Kapitels basieren auf der kostenfrei verfübaren JDO-Referenzimplementierung von SUN.
Diese beschränkt zwar die unterstützten Persistenzmechanismen auf ausschließlich dateibasierte Speicherung und
sieht keine Ablage in Datenbankmanagmenetsystemen vor.
Konzeptionell und programmierseitig ist die Interaktion mit dieser Implementierung jedoch identisch zu
kommerziell verfügbaren Lösungen und können daher ohne weiteres auf diese und damit beliebige
Persistenzdienstleister übertragen werden.
Die Abbildung der in der Programmiersprache formulierten Interaktionen auf den konkreten physischen
Persistenzdienstleister erfolgt sinnvollerweise an einer für alle JDO-nutzenden Applikationen
zugänglichen Stelle im Rahmen einer Property-Datei.
Die Inhalte dieser Datei unterscheiden naturgemäß bei den verschiedenen JDO-Herstellen und inhärent
mit dem gewählten Persistenztyp. So benötigt die dateibasierte Objektablage offenkundig andere
Festlegungen als der Zugriff auf ein relationales Datenbankmanagementsystem.
Beispiel 34 zeigt die notwendigen Einstellung
zur Konfiguration der dateibasierten Speicherung mit der SUN-Referenzimplementierung. Dort wird
mit der PersistenceManagerFactoryClass
diejenige Klasse innerhalb des JDO-Rahmenwerkes
benannt, welche dem Programmierer die Persistenzdienste zur Verfügung stellt. ConnectionURL
bildet das Bindeglied der Abbildung auf die physische Datei und benennt daher den Speicherort aller
persistierten Objekte. Die zusätzlichen Angaben dienen der Authentisierung und Zugriffssteuerung beim Zugriff
auf die erstellte Datei.
Beispiel 34: Konfiguration einer JDO-Implementierung | |
Download des Beispiels |
Die JDO-API ist im Rahmen des Java Community Prozesses als Java-Schnittstellensammlung nebst zugehöriger
Semantikdefinition spezifiziert. Die Implementierung der Schnittstellen erfolgt durch den Anbieter
der jeweiligen JDO-Implementierung und erfolgt auf den jeweiligen Persistenztyp abgestimmt.
Abbildung 5 zeigt die grundlegenden Schnittstellen der JDO-API
sowie die sie anbietenden Klassen der Referenzimplementierung.
Die Schnittstelle PersistenceCapable
bildet das Rückgrat der gesamten Persistenzbemühungen.
Jede Klasse, deren Speicherung durch JDO verwaltet werden soll (in Beispiel die Klasse Employee
)
muß diese Schnittstelle zwingend implementieren.
Typischerweise erfolgt diese Implementierung jedoch nicht direkt durch den Applikationsprogrammierer, sondern
wird im Rahmen der Bytecodeanreicherung nachträglich hinzugefügt.
Zur Interaktion mit Klassen, deren Implementierung der in PersistenceCapable
deklarierten
Methoden erst nach dem initialen Übersetzungsvorgang hinzugefügt werden kann der JDO-Anbieter die
Hilfsklasse JDOHelper
anbieten. Diese definiert verschiedene, ausschließlich als statisch deklarierte,
Methoden um mit Objekten von Klassen zu operieren, als würden diese die Schnittstelle
PersistenceCapable
umsetzen, ohne deren Klassen zur tatsächlichen Schnittstellenimplementierung
verpflichten.
Damit stellt JDOHelper
die unabdingbare Voraussetzung zur Anwendungsentwicklung
unter Verwendung der Bytecodeanreicherung dar, da diese erst nach dem Übersetzungsvorgang
Implementierungen derjenigen Schnittstellen hinzufügt, die bereits im Code verwendeten wurden.
Ferner bietet die Klasse die Möglichkeit den aktuellen Persistenzzustand eines
JDO-verwalteten Objektes auszulesen.
Zur Erzeugung von Objekten, die später den Zugriff auf das physische Speichermedium
regeln dienen die Umsetzungen der Schnittstelle PersistenceManagerFactory
. Sie erlaubt die
Parametrisierung und Verwaltung der Verbindung zum Persistenzmedium. Bereitgestellt wird die
Implementierung, im Falle der Referenzimplementierung, durch die Klasse
com.sun.jdori.fostore.FOStorePMF
.
Die Verbindung zwischen Schnittstelle und tatsächlicher Implementierung wird im Rahmen der
in Beispiel 34 gezeigten JDO-Konfiguration definiert. Zum Wechsel
des Persistenzanbieters -- etwa von der durch die Referenzimplementierung angebotenen dateibasierten
Speicherung auf eine datenbankgestützte Umsetzung -- genügt im die Abänderung dieses Eintrages in der
Konfigurationsdatei.
Klassen, welche die Schnittstelle PersistenceManagerFactory
implementieren, werden
zur Erzeugung von sog. PersistenceManger
n herangezogen. Umsetzungen dieser Schnittstelle
(im Falle der Referenzimplementierung ist dies die Klasse
com.sun.jdori.common.PersistenceManagerWrapper
) dienen zur Interaktion mit der Persistenzveraltung
innerhalb der JDO nutzenden Applikation. Alle Änderungen des Zustandes eines persistenten Objektes
werden durch diese Klasse abgewickelt.
JDO wickelt sämtliche Zugriffe auf die persistenten Daten transaktionsgesichert ab. Dieser Mechanismus
wird auf der abstrakten Ebene der API durch die Schnittstelle Transaction
definiert und steht
daher für alle Persistenzanbieter gleichermaßen zur Verfügung.
Die Schnittstelle definiert alle zur Transaktionssteuerung benötigten Operationen (darunter begin
,
commit
und rollback
) an.
Im Falle der Referenzimplementierung wird die Schnittstelle durch die Klasse
com.sun.jdori.common.query.QueryImpl
umgesetzt.
Zusätzlich sieht JDO eine abstrakte Möglichkeit zur Formulierung von Anfragen auf den verwalteten
Datenbestand vor. Die notwendige Schnittstelle wird durch Query
bereitgestellt.
Hierfür müssen die verschiedenen JDO-Implementierungen ebenfalls eigene Umsetzungen vorsehen.
Zur Erzeugung eines Objektspeichers ist bereits die Nutzung der Implementierungen der
zentralen JDO-Schnittstellen sowie die der Transaktionssteuerung notwendig.
Das Beispiel zeigt die notwendigen Schritte zur Erzeugung eines persistenten Objektspeichers.
Zunächst lädt das Beispiel die Konfiguration aus der Eigenschaftsdatei des Beispiels 34. Anschließend wird durch die mit true
belegte
implementierungsspezifische Eigenschaft com.sun.jdori.option.ConnectionCreate
festgelegt, daß im Rahmen des Verbindungsaufbaus auch notwendigenfalls der Objektspeicher neu erzeugt wird.
Die Interaktion mit JDO beginnt durch die Erzeugung eines PersistenceManagerFactory
konformen
Objektes durch den Aufruf getPersistenceManagerFactory
unter Auswertung der zuvor geladenen und
ergänzten Konfigurationseigenschaften.
Nach der Erzeugung des Factory-Objektes kann mittels diesem durch den Aufruf
getPersistenceManager
ein Objekt erzeugt werden, das die
Interaktion mit dem Objektspeicher bereitstellt. Durch die Ermittlung des Persistenzmanagers
wird gleichzeitig eine Verbindung zum Persistenzanbieter aufgebaut.
Ausgehend von diesem Verwaltungsobjekt kann durch Definition einer „leeren“ Transaktion --
d.h. einer Transaktion, die jenseits der Erzeugung des transaktionalen Kontexts und seines Abschlusses
mit committ, keine Operationen definiert -- der Objektspeicher erzeugt werden.
Den Abschluß der Interaktion mit dem Objektspeicher bildet die Beendigung der Verbindung durch
Ausführung der Methode close
des Verbindungsobjektes.
Beispiel 35: Erzeugung eines persistenten Objektspeichers | |
Download des Beispiels |
Grundsätzlich können Ausprägungen jeder beliebigen Javaklasse durch JDO persistiert werden, solange diese
Klassen die Schnittstelle PersistentCapable
explizit im Quellcode implementieren oder die
benötigte Implementierung im Rahmen der Bytecodeanreicherung hinzugefügt wird.
Zur Steuerung des konkreten Persistenzverhaltens wird eine zusätzliche Konfigurationsdatei benötigt.
Diese bedient sich der bekannten XML-Sytnax und definiert das Persistenzverhalten der durch JDO
zu verwaltenden Klasseninstanzen näher.
Beispiel 36 zeigt zunächst die zu persistierende
Klasse Employee
.
Beispiel 36: Zu persistierende Javaklasse | |
Download des Beispiels |
Die Nutzung JDO-gestützter Objektpersistenz impliziert keinerlei Modifikationen
oder Ergänzungen am Quellcode. Ebenso sind keinerlei Umsetzungskonventionen einzuhalten, die
im Beispiel definierten get
- und set
-Methoden dienen lediglich der vereinfachten
Interaktion.
Das Beispiel 37 illustriert eine
Parameterdatei zur Definition des spezifischen Persistenzverhaltens von Objekten der Klasse
Employee
.
Beispiel 37: Parametrisierung der Objektpersistenz | |
Download des Beispiels |
Die XML-Datei definiert zunächst den Paket- und Klassennamen der zu persistierenden Klasse mittels des
Attributs name
der XML-Elemente package
und class
.
Innerhalb eines class
-Elements kann für jedes Attribut der Javaklasse ein mit
field
benanntes Element zur näheren Charakterisierung des Speicherungsverhaltens angegeben werden.
Ein solches Element trägt zunächst im Attribut name
den klassenweit eindeutigen Namen
des Attributs und erlaubt die Festlegung des spezifischen Persistenzverhaltes mittels der Belegung des
Attributs persistence-modifier
. Ist dieses mit dem Wert persistent
versehen, so
wird ein so gekennzeichnetes Attribut durch JDO im Datenspeicher persistiert. Trägt das XML-Attribut den
Wert none
, so wird das Javaattribut bei der Abbildung in den JDO-Datenspeicher ignoriert.
Zusätzlich besteht die Möglichkeit durch die Belegung mit transactional
die Zwischenspeicherung
des Attributwertes während der Abarbeitung einer Transaktion zu erzwingen, um so eine spätere Wiederherstellung
(nach einem Aufruf von rollback
) zu gewährleisten. Jedoch werden Felder, die so gekennzeichnet sind,
nicht persistent in den Datenspeicher übernommen, sondern stehen nur während der Programmlaufzeit zur
Verfügung.
Fehlt diese Spezifikation zu einem Attribut in der XML-Datei, so wird vorgabegemäß die Belegung mit
persistent
angenommen, sofern es in der beherbergenden Javaklasse nicht als static
,
transient
oder final
ausgewiesen ist.
Attribute vom Typ einer Sammlungsklasse, wie sie durch die
Collection API
definiert werden müssen
zusätzlich mit einem collection
-Element, welches innerhalb des field
-Elements
plaziert ist, charakterisiert. Das collection
-Element spezifiziert durch sein
Attribut element-type
den Typ der Elemente in der Sammlung festlegt. Zusätzlich kann durch
das Boole'sche-Attribut embedded-element
gesteuert werden, ob die Inhalte des Sammlungsobjektes
zusammen mit dem die Sammlung referenzierenden Objekt persistiert werden sollen.
Das Beispiel legt für alle Attribute der Klasse Employee
ihre persistente Speicherung fest
(Belegung des XML-Attributs persistence-modifier
für alle Attribute persistent
);
ebenso wird die in Objekten des Typs Employee, unter dem Namen projects
, enthaltene
Sammlungsinstanz einschließlich ihrer Inhaltsobjekte des Standard-API-Typs String
dauerhaft
abgespeichert.
Über diese Festlegungen hinaus gestattet das Parametrisierungsformat die Festlegung spezifischer
Konsistenzsemantik in Gestalt der Auszeichnung eines Primärschlüssels. Dieses aus dem relationalen
Modell bekannte Konstrukt fordert die Eindeutigkeit eines Attributs oder einer Kombination von Attributen
über die gesamte Menge der Ausprägungen eines Typs.
Durch die Unterstützung als abstraktes JDO-Konstrukt steht dieses Konzept zur Konsistenzsicherung auch
für Applikationen zur Verfügung, die sich nicht relationaler Datenbanken als Persistenzdienstleister
bedienen.
Zur Realisierung des Primärschlüsselkonzeptes ist das als Schlüssel zu interpretierende Attribut
in der XML-Beschreibung zusätzlich mit dem XML-Attribut primary-key
zu versehen, welches
den Wert true
tragen muß. Zusätzlich ist innerhalb des Elements class
diejenige
Klasse anzugeben, welche das Attribut beherbergt, das als Schlüssel herangezogen werden soll.
38 zeigt die notwendigen Modifikationen an der
Parameterdatei des Beispiels 37 um das Java-Attribut
name
als Primärschlüssel festzulegen. Die primärschlüsselanbietende Klasse ist in diesem
Falle die Klasse Employee
selbst, weshalb sich ihr Name auch im XML-Attribut
objectid-class
des class
-Elements findet.
Beispiel 38: Parametrisierung der Objektpersistenz und Definition eines Primärschlüssels | |
Download des Beispiels |
Konsequenz der Einführung eines Primärschlüsselattributs ist die Überwachung der damit einhergehenden Konsistenzbedingungen durch das JDO-Laufzeitsystem. So führen Versuche zwei Objekte, die sich in der Belegung des als Primärschlüssel definierten Attributs nicht unterscheiden ebenso zu Fehlern wie schreibende Zugriffe auf dergestalt ausgezeichnete Attribute.
Voraussetzung der Persistenzverwaltung eines Objektes durch JDO ist die entsprechende Modifikation
dieses Objektes, konkret die Implementierung der in PersistenceCapable
festgelegten
Operationen durch Methoden der objekterzeugenden Klasse.
Dies wird jedoch nur in Ausnahmefällen durch den Applikationsprogrammierer direkt vorgenommen. Häufigste
Eionsatzform der JDO-API ist die Anwendung der Bytecodeanreicherung, welche die Implementierung der notwendigen
Funktionalität automatisiert vornimmt und diese nach dem eigentlichen Übersetzungsvorgang in den erstellten
Bytecode einbringt.
Abbildung 6 zeigt die daher notwendigen zwei Übersetzungsschritte.
Die Illustration versammelt die zur Erzeugung und Anreicherung des Bytecodes der per JDO zu persistierenden
Klasse Employee
aus Beispiel 36. Zur Anreicherung des
Bytecodes werden die in Beispiel 37 getroffenen Parametrisierungen
herangezogen.
Zunächst wird der im Paket de.jeckle.jdotest
abgelegte Quellcode Employee.java
mit dem Javacompiler in (gewöhnlichen) Bytecode übersetzt.
Anschließend wird dieser vermöge des in der JDO-Referenzimplementierung vorhandenen Werkzeuges
Enhancer
um die Implementierung der in der Schnittstelle PersistenceCapable
definierten Operationen angereichert. Hierzu wird dem Enhancer (bereitgestellt durch die Klasse
com.sun.jdori.enhancer.Main
zunächst das Zielverzeichnis des zu erzeugenden
Bytecodes mittels des Parameters d
übergeben. Naheliegernderweise kann der aus dem
ursprünglichen Bytecode durch Erweiterung erzeugte nicht die Ausgangsdatei überschreiben, daher
wird der angereicherte Bytecode im Verzeichnis enhanced
gespeichert. Zusätzlich
ist dem Enhancer der vollqualifizierte Name der anzureichernden Klasse sowie der vollqualifizierte
Pfad der Parameterdatei (im Beispiel: de/jeckle/jdotest/Employee.jdo
) zu übergeben.
Diese muß im Falle des Einsatzes der Referenzimplementierung zwingend die Extension jdo
besitzen.
Im Zusammenspiel zwischen transienter Objektverwaltung durch die Applikation im Hauptspeicher
und persistenter Objektverwaltung durch JDO im Hintergrundspeicher werden verschiedene Status eines
verwalteten Objekts unterschieden zwischen denen explizite Übergänge durch API-Aufrufe vorgegeben sind bzw.
implizit durch Operationen auf den involvierten Objekten bestehen.
Im Detail werden folgende Status unterschieden:
transient
gekennzeichnet sind.commit
noch rollback
abgeschlossenen) Transaktion erzeugt wurden.commit
bestätigt wurden.commit
dauerhaft gespeichert wurden und auf die noch kein Zugriff (weder lesend
noch schreibend) erfolgte.makeDirty
manuell in diesen
Zustand versetzt werden.refresh
werden die Inhalte von Haupt- und Hintergrundspeicher
synchronsiert, d.h. Inhalte des Hintergrundspeichers werden in den Hauptpeicher übernommen.Abbildung 7 zeigt die verschiedenen JDO-Status sowie die Ereignisse, die zu Zustandsübergängen führen, in der Übersicht.
Zur Speicherung von Objekten, deren Klassen durch den Bytecodeanreicherungsprozeß nachbearbeitet wurden,
bietet die JDO-API die Aufrufe makePersistent
und makePersistentAll
an. Diese
werden innerhalb eines Transaktionskontextes als Methoden eines PersistenceManager
-Objekte
ausgeführt.
Beispiel 39 zeigt die Speicherung von drei Objekten der
Klasse Emplyoee
, deren übersetzter Bytecode durch Anreicherung zur JDO-Kompatibilität
modifiziert wurde.
Zunächst wird mit empCol
vom Standardtyp Vector
eine Sammlungsobjekt zur
Aufnahme von Objektreferenzen definiert. Dieser Objektsammlung werden die Referenzen auf die erzeugten
Emplyoee
-Objekte (emp1
, emp2
und emp3
) hinzugefügt.
Als Voraussetzung der Interaktion mit dem Objektspeicher muß zunächst eine Transaktion eröffnet werden.
Hierzu muß zunächst durch Aufruf der Methode currentTransaction
die der
PersistenceManager
-Instanz zugeordnete Transaktion ermittelt werden. Ausgehend vom
gelieferten Ergebnisobjekt kann durch Ausführung der Methode begin
eine neuer
Transaktionskontext eröffnet werden.
Der Aufruf von makePersistentAll
persistiert bei Übergabe der Objektsammlung alle in der
Sammlung referenzierten Objekte. Alternativ können Einzelobjekte durch die Methode makePersistent
in den Zustand dauerhafter Speicherung überführt werden.
Zur Übernahme in den Hintergrundspeicher muß der Transaktionskontext durch Aufruf von commit
abgeschlossen werden. Der Aufruf von rollback
würde stattdessen alle in der Transaktion
vorgenommenen Änderungen verwerfen und auf den im Hintergrundspeicher verwalteten Datenzustand zurückgesetzt.
Beispiel 39: Speicherung von Objekten mit JDO | |
Download des Beispiels |
Würde wie im Beispiel 38 gezeigt das Attribut name
der Klasse Employee
als Primärschlüssel definiert sein, so würde der Persistierungsversuch
des durch emp3
referenzierten Objektes einen Laufzeitfehler liefern, da mit emp2
bereits ein Objekt mit derselben Belegung des Attributs name
persistiert wurde.
Treten während der Interaktion mit dem Persistenzspeicher, d.h. während eines noch nicht mit
commit
abgeschlossenen Transaktionskontextes Fehler auf, so können durch Aufruf
der Methode rollback
alle im aktuellen Kontext vorgenommen Änderungen auf den Stand
vor Beginn der Transaktion zurückgesetzt werden.
Beispiel 40 zeigt das Verhalten der Methode
rollback
am Beispiel. Durch die Schreiboperation innerhalb der geöffneten Transaktion
wird der Wert des Attributs name
zwar verändert, jedoch durch Aufruf von rollback
wieder auf den ursprünglichen Wert zurückgesetzt.
Beispiel 40: Transaktionen mit JDO | |
Download des Beispiels |
Die Ausführung des Beispiels liefert folgende Ausgabe:Employee named Marta Mayer works in department null
works in projects:
Martha gets married and changes her name
Employee named Marta Smith works in department null
works in projects:
Suppose and error happens now ...
Rolling back
Employee named Marta Mayer works in department null
works in projects:
Ist in bestimmten Anwendungsfällen die Arbeit ohne Transaktionsschutz -- und damit ohne
die Möglichkeit der expliziten Rücksetzung von Änderungen mittels rollback
oder der
impliziten Rücksetzung nach einem Systemausfall -- gewünscht, so kann dies durch Aktivierung der
Schreibfunktionalität ohne Transaktionsschutz erreicht werden.
Hierzu muß de Methode setNontransactionalWrite
mit dem Übergabeparameter true
für eine Transaktion aufgerufen werden.
Das nachfolgende Beispiel zeigt als Modifikation von Beispiel 39
die persistente Übernahme einer Wertänderung ohne Transaktionsschutz.
Beispiel 41: Schreiboperation ohne Transaktionsschutz | |
Download des Beispiels |
Zugriffe auf alle im Hintergrundspeicher verwalteten Objekte werden ebenfalls einheitlich durch
Methoden der Implementierung der Schnittstelle PersistenceManager
abgewickelt. Zur
Traversierung des vollständigen Bestandes aller Instanzen einer Klasse bietet diese Schnittstelle
die Operation getExtent
an. Sie liefert alle Elemente der Extension (d.h. der Gesamtheit
von Ausprägungen) einer gegebenen Klasse.
Beispiel 42 zeigt die Verwendung der Methode. Als Parameter
wird diejenige Klasse übergeben, deren Ausprägungen zu ermitteln sind. Zusätzlich kann durch einen
Boole'schen Schalter gesteuert werden, ob auch Subklassen der übergebenen Klasse retourniert werden sollen.
Der Aufruf liefert eine Sammlung von Objekten des Typs, welcher der Methode getExtent
übergeben wurde.
Beispiel 42: Traversierung des Objektbestandes | |
Download des Beispiels |
Als mächtige Alternative zur manuellen Traviersierung einer Objektextension spezifiziert JDO
die Verwendung einer eigenen Anfragesprache auf Basis des Standards der Object Query Language (OQL)
der Object Database Management Group (ODMG).
Diese -- als JDO Object Query Language (JDOQL) bezeichnete -- Anfragesprache ist direkt in die
JDO-API integriert und wird über verschiedene Einzelmethoden genutzt. Aus diesem Grunde sind JDOQL-Anfragen
nicht direkt mit den konsizsen SQL- oder OQL-Anfragen vergleichbar.
Beispiel 43 zeigt die Einbettung der Anfragesprache in die JDO-API.
Beispiel 43: Anfrage auf den persistenten Objektbestand mittels OQL | |
Download des Beispiels |
Das Beispiel illustriert eine Anfrage, die alle Employee
-Objekte liefert, deren
department
-Attribut mit dem Wert B042
belegt ist und liefert die
nach dem Inhalt des Attributes name
in aufsteigender Reihenfolge sortiert.
Hierzu wird zunächst die vollständige Extension der Klasse Employee
ermittelt. Allerdings
Extrahiert dieser Aufruf noch keine Werte aus dem persistenten Objektspeicher, sondern schafft nur
die Grundlagen einer späteren manuellen Traversierung oder der Anfrage via JDOQL.
Zur Vorbereitung der tatsächlichen physischen Anfrage wird zunächst eine Zeichenkette geeignet
belegt, um als Filterausdruck dienen zu können, der auf die vollständige Extension angewandt wird.
Im Beispiel ist dieser Filterausdruck mit department == \"B042\"
belegt. Aus Gründen
der Zeichenkettenverarbeitung in Java muß hierzu der notwendige Einschluß des zu suchenden Wertes
in Anführungszeichen geeignet maskiert werden.
Nach diesen Vorbereitungsschritten kann durch den Aufruf der durch das PersistenceManager
-kompatible
Objekt bereitgestellten Methode newQuery
ein neues Anfrageobjekt (vom Typ Query
)
erzeugt werden.
Dieses Objekt erlaubt nach der gezeigten Festlegung des Anfrageumfanges die Parametrisierung der Anfrage.
Das Beispiel illustriert dies am Aufruf der Methode setOrdering
, die es erlaubt eine
bestimmte Sortierreihenfolge der gelieferten Ergebnisse vorzugeben.
Zusätzlich kann durch die optionale Ausführung der Methode compile
eine Prüfung der zusammengestellten
Anfrage erfolgen, die zusätzlich auch interne implementierungsspezifische Optimierungen vornehmen kann.
Abschließend erfolgt die Ausführung der Anfrage durch Aufruf der Methode execute
, welche die
Anfrageergebnisse konform zur Standardschnittstelle Collection
zurückliefert.
Zur Entfernung eines Objektes aus dem Objektspeicher stellt die Schnittstelle
PersistenceManager
die Methode deletePersistent
zur Verfügung, welche
ein einzelnes hauptspeicherresidentes Objekt aus dem persistenten Speicher löscht, bzw.
mit deletePersistentAll
eine Möglichkeit alle durch eine Sammlung referenzierten
Objekte zu entfernen.
Da es sich hierbei um einen schreibenden Zugriff handelt, muß dieser in einen Transaktionskontext
eingebettet werden oder explizit transaktionslos durchgeführt werden wie in Beispiel 41 gezeigt.
Beispiel 44 zeigt die Löschung unter Verwendung eines
Transaktionskontextes.
Beispiel 44: Löschen eines Objektes aus dem persistenten Objektbestand | |
Download des Beispiels |
Der JDO-Ansatz tritt mit dem Versprechen auf vollständig sowohl unabhängig vom verwendeten
Persistenzmedium (etwa: Datenbank, Dateisystem, etc.) als auch der eingesetzten JDO-Implementierung zu sein.
Diese Zielsetzung wird nachfolgend auf Basis des im vorhergehenden diskutierten Employee
-Beispiels
untersucht. Hierzu wird die frei verfügbare JDO-Implementierung TJDO eingesetzt, welche verschiedene
Datenbankmanagementsysteme zur Speicherung der Javaobjekte heranziehen kann. Im Beispiel wird das
DBMS MySQL Persistierung der Applikationsobjekte genutzt.
Zur Portierung der bestehenden Applikation ist lediglich die Anpassung der JDO-Eigenschaften
(Property-Datei) vorzunehmen, um den neuen Persistenzdienstleister sowie die verschiedenen DBMS-Spezifika
zu berücksichtigen.
Beispiel 45 zeigt die neuen Inhalte.
Beispiel 45: Konfiguration der JDO-Implementierung TJDO | |
Download des Beispiels |
Zunächst werden die bereits in der Konfiguration der Referenzimplementierung durch Beispiel
34 genutzten Eigenschaften zur Identifikation
derjenigen Klasse, welche die JDO-Schnittstelle PersistenceManagerFactory
implementiert sowie
zur Festlegung der Verbindungs-URL und des zu verwendenden Benutzernamens uns Passwortes an die neuen
Gegebenheiten adaptiert. Konkret wird die durch TJDO bereitgestellte Klasse
com.triactive.jdo.PersistenceManagerFactoryImpl
als PersistenceManagerFactory
konforme Implementierung sowie die Identifikation der zu verwendenden Datenbank nebst Benutzername
und Anmeldekennwort bekanntgegeben.
Zusätzlich wird mit com.triactive.jdo.autoCreateTables
eine implementierungsspezifische
Eigenschaft mit true
belegt, die TJDO veranlaßt im Bedarfsfalle benötigte Tabellenstrukturen
automatisiert zu erzeugen.
Zusätzlich erfordert die verwendete JDO-Implementierung die Adaption der im Rahmen des Bytecodeanreicherungsprozesses herangezogenen Konfigurationsdatei (Beispiel 46). Auf diesem Wege wird dem Programmierer die Möglichkeit eröffnet die Abbildung auf relationale Tabellenstrukturen beeinflussen. In der Konsequenz erfordert der Wechsel der JDO-Implementierung die Wiederholung des Anreicherungslaufes für den Bytecode der zu persistierenden Klassen.
Beispiel 46: Parametrisierung der Objektpersistenz | |
Download des Beispiels |
Weitere Änderungen an den zu persistierenden Klassen oder den mit deren Objekten operierenden Applikationen ist nicht notwendig, alle Zugriffe werden nach den oben beschriebenen Änderungen transparent und ohne Neuübersetzung datenbankbasiert abgewickelt.
Abschließend seien die charakteristischen Eigenschaften der drei diskutierten Persistenzansätze JDBC, EJB und JDO kurz vergleichend nebeneinandergestellt.
|
Die Tabelle zeigt klar, daß alle drei Persistenzmechanismen grundlegende Eigenschaften
teilen, sich jedoch auch in zentralen Charakteristika unterscheiden.
Während sowohl JDBC als auch EJBs die direkte Verwendung von SQL-Anfragen gestatten bietet
JDO mit JDOQL eine eigenständige Anfragesprache, die direkt in die Sprach-API eingebettet ist.
Für EJBs existiert neben den in Kapitel 1.2 gezeigten Mechanismen auch die Möglichkeit der
Verwendung der EJB-spezifischen Anfragesprache EJBQL, die jedoch hier nicht betrachtet wurde.
Hinsichtlich der jeweils unterstützten Hintergrundspeicherarchitekturen zur Realisierung der Persistenz
treten jedoch deutliche Unterschiede zu Tage. So ist der Einsatz der JDBC-API auf relationale Datenquellen,
bzw. Datenquellen die eine relationale Sicht anbieten, beschränkt. Innerhalb der EJB-Architektur können
hingegen neben den -- hier diskutierten JDBC-basierten Mechanismen -- auch die Dienste einer
Integrationsmiddleware zu Speicherung herangezogen werden und so eine gewisse Unabhängigkeit vom
physischen Speichermedium erreicht werden. Einzig JDO bietet durch seine starke Abstraktion
die Möglichkeit beliebige Persistenzdienstleister zu nutzen.
Zur effizienten Abwicklung dieses speicherformunabhängigen Zugriffs etabliert JDO notwendigerweise
eine stark abstrahierte API, deren Funktionen keinerlei Rückschlüsse auf den verwendeten Persistenzmechanismus
zulassen. Für EJB läßt sich dies prinzipiell auch realisieren, allerdings müssen für die Variante
der bean managed persistence innerhalb der Entity Bean die Interaktionen mit dem Persistenzdienstleister
expliziert werden, beispielsweise durch JDBC. Daher verhält sich dieser Ansatz intern ähnlich zur direkten
Verwendung der JDBC-API, die inhärent jeden angebundenen Persistenzmechanismus mit relationaler
Zugriffssemantik belegt.
Aufgrund des vorherrschenden relationalen Speicherparadigmas kann die Einbindung bestehender Tabellenstrukturen
in den API-Mechanismus gewünscht sein. Dies ist ausschließlich mit Ansätzen möglich, welche die
anwenderdefinierte Strukturierung der Zugriffsausdrücke -- etwa durch die Verwendung von SQL -- gestatten. Dies
ist ausschließlich für JDBC und EJB (sofern bean managed persistence verwendet wird) möglich; JDO sieht
dies generell nicht vor.
Abschließend lassen sich die vorgestellten Schnittstellen hinsichtlich ihrer Möglichkeiten zur
Bereitstellung eines transparenten Zugriffs auf den Hintergrundspeicher und der manuellen Eingriffsmöglichkeiten
zur Kontrolle der Persistenz durch den Programmierer kategorisieren.
Prinzipiell läßt sich festhalten, daß diese Eigenschaftstypen konkurrierende Zielsetzungen darstellen.
So bietet JDBC zweifelsohne die größten Möglichkeiten zum steuernden Eingriff durch den Programmierer, wobei
dieser Ansatz in der Interaktion auch die größte Menge Wissen des Programmierers über die etablierten
Speicherstrukturen erfordert. Daher realisiert JDBC generell die geringste Transparenz im Zugriff auf
den Objektspeicher.
Auf der anderen Seite realisiert JDO die größtmögliche Transparenz im Objektzugriff, wobei dieser Freiheitsgrad
zu generell zu Lasten der Eingriffsmöglichkeiten durch den Programmierer umgesetzt werden.
Neben den Basistechniken und bisher vorgestellten Schnittstellen zur Realisierung von Persistenz
finden gegenwärtig eine Reihe von Architekturmustern und Handreichungen zur Umsetzung der Verbindung
von dauerhafter Datenspeicherung und Anwendungsprogrammierung Einsatz.
Ziel dieses Kapitels ist es, ausgewählte Muster an Beispielen vorzustellen und ihren Einsatz in den
Kontext der im Abschnitt eins eingeführten Schnittstellentechniken zu stellen.
Hierzu werden die Muster zunächst in drei Abschnitte gegliedert und gleichzeitig im Hinblick auf die durch
sie angesprochene Problemdomäne kategorisiert.
Die vorgestellten Muster und Beispiele orientieren sich an den im Buch Patterns of Enterprise Application Architecture von M. Fowler vorgestellten.
Die nachfolgend eingeführten Muster zur Abbildung von Datenbank-gestützt operierender Applikationslogik in Programmstrukturen. Durch die Anwendung der diskutierten Mechanismen wird eine Entkopplung zwischen Datenbankoperationen und Domänen-induzierten Operationen der Applikationsebene angestrebt, um diese voneinander separiert entwickeln und modifizieren zu können.
Motivation und Grundidee: Zur Abbildung komplexer Geschäftsoperationen sind in der Regel
eine Reihe von eigenständigen Datenbankinteraktionen notwendig.
Werden einzelne dieser Interaktionen außerhalb ihres logischen
Kontexts ausgeführt, so kann dies zu Inkonsistenzen des verwalteten Datenbestandes führen.
Gleichzeitig setzt die korrekte Ausführung der einzelnen Datenbankoperationen die Kenntnis der Abbildung
des Geschäftsprozesses auf die technischen Strukturen voraus um gültige Abläufe konstruieren zu können.
Ziel der Anwendung der Transaktionsskripte (engl. transaction script) ist es jeden
Geschäftsablauf durch genau eine Applikationsmethode abzubilden, welche die notwendigen Datenbankinteraktionen
zuverlässig kapselt.
Hierbei impliziert der Terminus der Transaktion nicht zwingend die Nutzung von Datenbanktransaktionen
im Sinne der ACID-Prinzipien.
Struktur: Typischerweise werden die Geschäftsoperationen durch eine oder mehrere Domänenklassen, d.h. Klassen deren Struktur und Logik nicht auf Basis technischer Erwägungen und Notwendigkeiten gebildet wurde, zur Verfügung gestellt. Diese Klassen treten als Dienstleister gegenüber dem Applikationsprogramm auf.
Abbildung 9 zeigt die Grundstruktur des Dienstangebotes einer Bank
.
Sie bietet Überweisungen eines Geldbetrages von einem Konto zum anderen sowie die Möglichkeit der Erstellung
einer Vermögensübersicht für einen Kunden an.
Die verwalteten Daten seien in diesem Beispiel in einer relationalen Datenbank abgelegt, die aus
zwei Tabellen (Konto
und Inhaber
) besteht. Abbildung 10
zeigt den Datenbankaufbau.
Der Aufbau der Datenbank und ihre Befüllung mit Beispieldaten geschieht durch das folgende SQL-Script:
Beispiel 47: Erzeugung und Befüllung der Datenbank mit SQL | |
Download des Beispiels |
Das Transaktionsskript: Beispiel 48 zeigt die das Transaktionsskript realisierende Klasse. Sie kapselt die Datenbankinteraktion vollständig und stellt die beiden in Abbildung 9 gezeigten Methoden zur Verfügung.
Beispiel 48: Transaktionsskript zur Interaktion mit der Bank | |
Download des Beispiels |
Die gesamte Interaktion mit der Persistenzlogik geschieht durch die beiden Geschäftsmethoden und bedarf keiner Kenntnis und Berücksichtigung der technischen Datenbankcharakteristika. Beispiel 49 zeigt die Nutzung der beiden angebotenen Methoden.
Beispiel 49: Nutzung des Transaktionsskriptes | |
Download des Beispiels |
Umsetzung unter Einsatz des Command Musters:
Die Nutzung von Transaktionsskripten führt zur Bildung von Methoden, die mit Namen aus der Geschäftsdomäne
belegt sind. So treten im Beispiel 48 die Methoden
Überweisung
und Vermögensübersicht
auf.
Diese Benennungseigenschaft ist jedoch nicht immer gewünscht. Vielmehr strebt man bei der Implementierung
häufig eine gleichartige Aufrufschnittstelle verschiedener Sachverhalte an. Daher findet sich häufig
Transaktionsskripte mithilfe des Command Musters umgesetzt.
Dieses Muster definiert für alle aufrufbaren Domänenmethoden des Transaktionsskriptes eine einheitliche
Schnittstelle (im Beispiel durch die Methode run
verkörpert).
Der Einsatz des Musters hat jedoch keinen Einfluß auf die verwirklichte Domänenlogik, sondern ändert nur
die Aufrufmimik.
Die Beispiele 50 mit 52 zeigen die modifizierte Umsetzung der einzelnen Methoden des Transaktionsskripts, die nun durch separate Klassen repräsentiert werden. Die Kontroll-Logik bleibt jedoch gegenüber der vorhergehenden Lösung unverändert.
Beispiel 50: TransactionScript.java | |
Download des Beispiels |
Beispiel 51: Überweisung.java | |
Download des Beispiels |
Beispiel 52: Vermögensübersicht.java | |
Download des Beispiels |
Bei der Erstellung von Applikationen, welche die vereinheitlichten Schnittstellen nutzen
zeigt sich das Resultat in Form einer gleichartigen Aufrufschnittstelle
(im Beispiel die Methode run
) für die verschiedenen
Domänenmethoden:
Beispiel 53: Driver2.java | |
Download des Beispiels |
Abschließende Würdigung:
Das Transaktionsskript-Muster bietet eine vergleichsweise einfach nachvollziehbare Möglichkeit zur
Entkopplung von Persistenz- und Geschäftslogik an. Jedoch tritt sehr schnell (wie in den Beispielen
51 und 52
anhand der Methode connectDB
gezeigt) die Gefahr auf, daß gleichartiger Code in verschiedene
Transaktionsskripte zu integrieren ist.
Überdies führen komplexe Geschäftslogiken, die sich partiell überlappen und gegenseitig enthalten zu
aufwendigen Entwürfen in denen Coderedundanz nicht immer zu vermeiden ist.
Abhilfe kann hier die Verwendung eines Domänenmodells bieten.
Motivation und Grundidee: Hintergrund der Strukturform des Domain Models ist
der Versuch die in der Analysephase vorgefundenen Objekte des betrachteten Problembereichs
möglichst unverändert durch die Applikation zur Verfügung zu stellen und in die Datenbank zu übernehmen.
Im Gegensatz zur Strukturierungsform des Transaction Scripts erfolgt der Anwendungsaufbau
hierbei nicht an den Geschäftsprozessen orientiert, sondern rein Daten-getrieben.
Struktur: Im Idealfall entsprechen sich die Struktur der applikationsimmanenten Klassen und
die der datebankresidenten Tabellen eineindeutig. Abweichungen davon können sich lediglich durch
Tabellen ergeben, welche dieselbe Realweltentität abbilden. Diese können durch den Normalisierungsprozeß
gebildet worden sein.
Das Beispiel der Abbildung 11 zeigt die Klassenstruktur
einer Projektverwaltung, in der die Zuordnungen zwischen Person
en und den Projekt
en
in denen diese eingesetzt sind verwaltet werden.
Jeder Ausprägung von Person
können hierbei mehrere Objekte des Typs Projekt
zugeordnet sein und umgekehrt.
Zusätzlich sind die für die jeweiligen Klassen definierten Operation dargestellt. Hierbei
kann es sich um triviale Operationen zum Setzen und Auslesen einzelner Attributwerte oder
beliebig aufwendige Vorgänge handeln. Gemeinsames Kennzeichen aller Operationen ist jedoch,
daß sie nur dasjenige Objekt betreffen in dessen Kontext sie definiert sind.
Die verwalteten Daten sind in drei Datenbanktabellen abgelegt. Abbildung 12 zeigt die Struktur der Tabellen einschließlich
der definierten Fremdschlüsselbeziehungen.
Aus Gründen der Normalisierung (die Relation befindet sich in
vierter Normalform) wird zusätzlich
die Tabelle WorksOn
eingeführt, welche die Daten über die Zuordnung zwischen
Projekt
en und den sie bearbeitenden Person
en enthält.
Der Aufbau der Datenbank geschieht durch das folgende SQL-Script:
Beispiel 54: DB-Aufbau | |
Download des Beispiels |
Das Domänenmodell: Die Beispiele 55 und
56 zeigen die beiden fachlichen Domänenklassen
Person
und Projekt
.
Beispiel 55: Die Domänenklasse Person | |
Download des Beispiels |
Beispiel 56: Die Domänenklasse Projekt | |
Download des Beispiels |
Zusätzlich ist aus Gründen der vereinfachten Interaktion mit dem Datenbankmanagementsystem
die Klasse DBConnector
umgesetzt.
Beispiel 57: Die Klasse DBConnector | |
Download des Beispiels |
Die beiden Domänenklassen kapseln die Interaktion mit der Datenbank vollständig
vor dem Aufrufer. Alle Persistenzoperationen werden in Form einfacher („low-level“)
Operationen zur Verfügung gestellt. Die durch eine Klasse angesprochenen Datenbanktabellen sind
dabei streng auf diejenigen beschränkt, welche die durch die Klasse verwaltenden Daten aufnehmen.
Auffallend ist, daß die Domänenobjekte außer den Attributen, die den Primärschlüssel repräsentieren,
keine durch Attribute ausgedrückte Eigenschaften besitzen. Diese werden ausschließlich in der
Datenbank repräsentiert und im Bedarfsfalle angefragt (Beispiel: Realisierung der Methode
getGeburtsdatum
der Klasse Person
).
Die Begründung hierfür wird bei der Analyse der Zugriffe auf die Tabelle WorksOn
offenkundig.
Da diese Tabelle durch die beiden Domänenobjekte unabhängig voneinander zugegriffen werden kann, würde
eine (redundante) Datenverwaltung im Hauptspeicher tendenziell zu Konsistenzproblemen mit durch
das Datenbankmanagementsystem verwalteten Daten führen.
Einen solchen Fall, der durch die gewählte Umsetzung korrekt behandelt wird, zeigt der in Beispiel 58 wiedergegebene Code:
Beispiel 58: Domänenmodell verwendende Applikation | |
Download des Beispiels |
Im Beispiel werden zunächst Ausprägungen des Typs Person
und Projekt
erzeugt und manipuliert. Hierbei wird für dasselbe Person
en-Objekt eine Zuordnung
dieses Objekts zu einem Projekt vorgenommen und anschließend diesem Projekt eine andere Person
zugeordnet. Diese beiden Interaktionen entsprechen der Instanziierung der die beiden
Domänenklassen verbindenden Assoziation mit jeweils unterschiedlichen Ausgangspunkten der
Beziehungsetablierung.
Nur durch den Rückgriff auf die Datenbankinhalte liefert der Aufruf von getAllMitarbeiter
konsistente Daten, da sowohl innerhalb der Person
- als auch der Projekt
-Ausprägung
nur unvollständige (d.h. diejenigen durch den jeweiligen add...
-Aufruf erzeugten)
Daten vorliegen.
Abschließende Würdigung: Das Domänenmodell ermöglicht eine vergleichsweise einfache Abbildung
der Objektstrukturen des Hauptspeichers in relationale Datenbankstrukturen.
Allerdings ist die Abbildung komplexer Abhängigkeitsstrukturen der objektorientierten Applikationsdaten
in relationale Tabellenstrukturen mitunter schwierig; insbesondere wenn hinsichtlich der Güte der
entstehenden DB-Strukturen zusätzliche Qualitätskriterien (wie Redundanzfreiheit durch Normalisierung)
angelegt werden.
Grundsätzlich bietet dieses Umsetzungsmuster den Vorteil aus Sicht des Fachanwenders „naheliegende“
Entitäten bereitstellen zu können.
Die Interaktion auf der Basis der durch Domänenklassen angebotenen Operationen kann, abhängig
vom Komplexitätsgrad der zu realisierenden Anwendung, -- im Vergleich zum Ansatz des Transaction
Scripts -- aufwendig sein. Insbesondere offenbart sich die vermöge der Domänenoperationen etablierte
Abstraktionsschicht als Hemmnis, wenn eine direkte Interaktion mit den wertrepräsentierenden
Tabellen intendiert ist.
In diesen Fällen eignet sich ein Table Module besser zur Realisierung.
Service provided by Mario Jeckle
Generated: 2004-06-11T07:11:51+01:00
Feedback SiteMap
This page's original location: http://www.jeckle.de/vorlesung/db-anwendungen/script.html
RDF description for this page