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
Die für die Programmiersprache
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.
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
Die historisch älteste Variante bildet der
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
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
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
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
Die Vorteile der
Zwar spricht der leichte Installations- und
Adminstrationsaufwand eindeutig für
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
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
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
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
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
Beispiel 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.
Zusätzlich stellen die Klassen Driver
und
DriverManager
die Möglichkeit der Abfrage von
verbindungsunabhängigen Verwaltungsinformationen zur
Verfügung.
Beispiel 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
Seit der JDBC-Schnittstellenversion 2 ist neben der
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 MysqlDataSource
-Objekts, der durch den MySQL-JDBC-Treiber gelieferten
Implementierung der Schnittstelle
DataSource
.
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
Der Code des Beispiels DataSource
aus dem JNDI-Verzeichnis,
sowie die Erzeugung des Connection
-Objekts.
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
Zur Optimierung von Zugriffen dieser Natur sieht die JDBC-Schnittstelle das sog. Connection Pooling vor,
welches gleichartige Zugriffe bündelt.
Das Beispiel
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 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 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 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
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.
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
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
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
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.
Neben im Beispiel
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
.
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.
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
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.
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
Analog vollzieht sich der Löschvorgang mittels deleteRow:
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
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
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";
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
Die Beispieldatenbank wurde hierfür um ein Attribut zur Aufnahme binärer Daten erweitert:
ALTER TABLE EMPLOYEE ADD binData blob;
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:
commit
freigegebene
Daten werden gelesen. Es können daher dirty reads, Nicht-wiederholbare- und Phantomlesevorgänge
auftreten.commit
freigegebene
Daten werden gelesen. nichtwiederholbare- und Phantomlesevorgänge können jedoch auftreten.true
bewirkt die Aktivierung des Modus bei
dem jede Einzelanweisung sofort persistent übernommen und für andere Transaktionen sichtbar wird.Beispiel
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.
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 setSavepoint
zur Definition eines Sicherungspunktes und
rollback(sp)
zum Zurücksetzen bis zu diesem Sicherungspunkt.
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.
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
Gegenüber der Realisierung als RMI-Anwendung benötigt die Umsetzung als zustandslose Session Bean die Erstellung einer sog. create
zur Instanziierung des serverseitigen EJB-Objekts bietet.
Sie ist im Beispiel
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
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
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
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
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
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
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:
Operation | Semantik | Zugehörige SQL-Anweisung |
ejbCreate
|
Wird nach dem Erzeugen eines Java-Objektes aufgerufen um dieses in der Datenbank abzulegen. Diese Operation ist nicht Bestandteil der Schnittstelle, da ihre Parameter, die den Übergabeparametern des Objektkonstruktors entsprechen, zum Schnittstellenerzeugungszeitpunkt nicht feststehen. |
INSERT
|
ejbFindByPrimaryKey
|
Liefert den Wert des Primärschlüssels zurück, sofern ein Datenbankeintrag existiert, der durch diesen Primärschlüssel identifiziert wird. Diese Operation ist nicht Bestandteil der Schnittstelle, da ihre Parameter, die in Typ, Name und Reihenfolge der Zusammensetzung des Primärschlüssels entsprechen, zum Schnittstellenerzeugungszeitpunkt nicht feststehen. Diese Methode wird nicht durch den Anwender direkt aufgerufen, sondern stattdessen auf einer Ausprägung der Home-Schnittstelle das dort zur Verfügung stehende Analogon findByPrimaryKey , welches das durch den Primärschlüssel identifizierte EJB-Objekt zurückliefert. Diese Methode greift intern auf ausschließlich den Schlüssel liefernde Methode der Bean zu.
|
SELECT
|
ejbRemove
|
Entfernt das EJB-Objekt aus der Datenbank. |
DELETE
|
ejbStore
|
Synchronisiert das Java-Objekt mit dem EJB-Objekt und aktualisiert so die Datenbankinhalte. Diese Methode wird nach jedem Zugriff mittels einer in der Remote-Schnittstelle aufgeführten Operation auf das EJB-Objekt ausgeführt. |
UPDATE
|
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
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
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 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.
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.
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
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 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
Den Abschluß der Interaktion mit dem Objektspeicher bildet die Beendigung der Verbindung durch
Ausführung der Methode close
des Verbindungsobjektes.
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 Employee
.
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 Employee
.
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.
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.
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.
Die Illustration versammelt die zur Erzeugung und Anreicherung des Bytecodes der per JDO zu persistierenden
Klasse Employee
aus Beispiel
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.
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 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.
Würde wie im Beispiel 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 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.
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
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
Der Aufruf liefert eine Sammlung von Objekten des Typs, welcher der Methode getExtent
übergeben wurde.
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
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
Beispiel
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
Zunächst werden die bereits in der Konfiguration der Referenzimplementierung durch Beispiel
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
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
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.
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.
Der Aufbau der Datenbank und ihre Befüllung mit Beispieldaten geschieht durch das folgende SQL-Script:
Das Transaktionsskript: Beispiel
Die gesamte Interaktion mit der Persistenzlogik geschieht durch die beiden Geschäftsmethoden und
bedarf keiner Kenntnis und Berücksichtigung der technischen Datenbankcharakteristika. Beispiel
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 Ü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
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:
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
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 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.
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:
Das Domänenmodell: Die Beispiele Person
und Projekt
.
Zusätzlich ist aus Gründen der vereinfachten Interaktion mit dem Datenbankmanagementsystem
die Klasse DBConnector
umgesetzt.
Die beiden Domänenklassen kapseln die Interaktion mit der Datenbank vollständig
vor dem Aufrufer. Alle Persistenzoperationen werden in Form einfacher (
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
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
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.