Objektpersistenz und Java

Objektbeständigkeit oder Persistenz ist der Begriff, den Sie häufig im Zusammenhang mit dem Speichern von Objekten in Datenbanken hören. Es wird erwartet, dass die Persistenz mit Transaktionsintegrität funktioniert, und als solche unterliegt sie strengen Bedingungen. (Weitere Informationen zur Transaktionsverarbeitung finden Sie im Abschnitt Ressourcen dieses Artikels.) Im Gegensatz dazu sind Sprachdienste, die über Standard-Sprachbibliotheken und -Pakete angeboten werden, häufig frei von Transaktionsbeschränkungen.

Wie wir in diesem Artikel sehen werden, deuten die Beweise darauf hin, dass die einfache Java-Persistenz wahrscheinlich von der Sprache selbst herrührt, während Datenbankanbieter ausgefeilte Datenbankfunktionen anbieten.

Kein Objekt ist eine Insel

In der realen Welt finden Sie selten ein Objekt, dem Beziehungen zu anderen Objekten fehlen. Objekte sind Bestandteile von Objektmodellen . Das Problem der Objektbeständigkeit geht über das Problem der Objektmodellbeständigkeit und -verteilung hinaus, wenn wir beobachten, dass Objekte aufgrund ihrer Beziehungen zueinander miteinander verbunden sind.

Der relationale Ansatz zur Datenspeicherung tendiert dazu, Daten nach Typ zu aggregieren. Zeilen in einer Tabelle repräsentieren das physische Aggregat von Objekten desselben Typs auf der Festplatte. Die Beziehungen zwischen Objekten werden dann durch Schlüssel dargestellt, die von vielen Tabellen gemeinsam genutzt werden. Durch relationale Datenbanken können durch relationale Datenbanken Tabellen, die wahrscheinlich zusammen verwendet werden, manchmal in derselben logischen Partition, z. B. einem Datenbanksegment, zusammen lokalisiert (oder geclustert ) werden. Sie verfügen jedoch nicht über einen Mechanismus zum Speichern von Objektbeziehungen in der Datenbank. Um ein Objektmodell zu erstellen, werden diese Beziehungen zur Laufzeit aus den vorhandenen Schlüsseln in einem Prozess erstellt, der als Tabellenverknüpfungen bezeichnet wird . Dies ist dieselbe bekannte Eigenschaft der aufgerufenen relationalen DatenbankenDatenunabhängigkeit . Nahezu alle Varianten von Objektdatenbanken bieten einen Mechanismus zur Verbesserung der Leistung eines Systems, das komplexe Objektbeziehungen gegenüber herkömmlichen relationalen Datenbanken umfasst.

Abfragen oder navigieren?

Beim Speichern von Objekten auf der Festplatte stehen wir vor der Wahl, verwandte Objekte zusammen zu lokalisieren, um den Navigationszugriff besser zu ermöglichen, oder Objekte in tabellenartigen Sammlungen zu speichern, die Objekte nach Typ aggregieren, um den prädikatenbasierten Zugriff (Abfragen) oder beides zu erleichtern . Die gemeinsame Positionierung von Objekten im persistenten Speicher ist ein Bereich, in dem sich relationale und objektorientierte Datenbanken stark unterscheiden. Die Wahl der Abfragesprache ist ein weiterer Gesichtspunkt. Strukturierte Abfragesprache (SQL) und Erweiterungen davon haben relationalen Systemen einen prädikatenbasierten Zugriffsmechanismus bereitgestellt. Object Query Language (OQL) ist eine von ODMG standardisierte Objektvariante von SQL. Die Unterstützung für diese Sprache ist derzeit jedoch gering. Polymorphe Methoden bieten beispiellose Eleganz bei der Erstellung einer semantischen Abfrage für eine Sammlung von Objekten. Zum Beispiel,Stellen Sie sich ein polymorphes Verhalten für voracccountgenannt isInGoodStanding. Es kann den Booleschen Wert true für alle Konten mit gutem Status und andernfalls false zurückgeben. Stellen Sie sich nun die Eleganz vor, die Sammlung von Konten, inGoodStandingdie je nach Geschäftsregeln unterschiedlich implementiert ist, für alle Konten mit gutem Status abzufragen. Es könnte ungefähr so ​​aussehen:

setOfGoodCustomers = setOfAccounts.query(account.inGoodStanding());

Während einige der vorhandenen Objektdatenbanken einen solchen Abfragestil in C ++ und Smalltalk verarbeiten können, ist es für sie schwierig, dies für größere Sammlungen (z. B. mehr als 500 Gigabyte) und komplexere Abfrageausdrücke zu tun. Einige der relationalen Datenbankunternehmen wie Oracle und Informix werden in Kürze eine andere SQL-basierte Syntax anbieten, um das gleiche Ergebnis zu erzielen.

Ausdauer und Typ

Ein objektorientierter Sprachliebhaber würde sagen, dass Persistenz und Typ orthogonale Eigenschaften eines Objekts sind. Das heißt, persistente und transiente Objekte desselben Typs können identisch sein, da eine Eigenschaft die andere nicht beeinflussen sollte. Die alternative Ansicht besagt, dass Persistenz ein Verhalten ist, das nur von persistenten Objekten unterstützt wird, und dass bestimmte Verhaltensweisen möglicherweise nur für persistente Objekte gelten. Der letztere Ansatz erfordert Methoden, die persistente Objekte anweisen, sich selbst zu speichern und aus dem persistenten Speicher abzurufen, während der erstere der Anwendung eine nahtlose Ansicht des gesamten Objektmodells bietet - häufig durch Erweiterung des virtuellen Speichersystems.

Kanonisierung und Sprachunabhängigkeit

Objekte desselben Typs in einer Sprache sollten unabhängig von der Reihenfolge, in der ihre Schnittstellen angezeigt werden, in einem dauerhaften Speicher mit demselben Layout gespeichert werden. Die Prozesse zum Transformieren eines Objektlayouts in dieses gemeinsame Format werden zusammen als Kanonisierung der Objektdarstellung bezeichnet. In kompilierten Sprachen mit statischer Typisierung (nicht Java) sollten Objekte, die in derselben Sprache geschrieben, aber unter verschiedenen Systemen kompiliert wurden, im persistenten Speicher identisch dargestellt werden.

Eine Erweiterung der Kanonisierung befasst sich mit der sprachunabhängigen Objektdarstellung. Wenn Objekte sprachunabhängig dargestellt werden können, können verschiedene Darstellungen desselben Objekts denselben persistenten Speicher gemeinsam nutzen.

Ein Mechanismus, um diese Aufgabe zu erfüllen, besteht darin, eine zusätzliche Indirektionsebene über eine Interface Definition Language (IDL) einzuführen. Objektdatenbankschnittstellen können über die IDL und die entsprechenden Datenstrukturen hergestellt werden. Die Nachteile von IDL-Stilbindungen sind zweierlei: Erstens erfordert die zusätzliche Indirektionsebene immer eine zusätzliche Übersetzungsebene, die sich auf die Gesamtleistung des Systems auswirkt. Zweitens wird die Verwendung von Datenbankdiensten eingeschränkt, die nur für bestimmte Anbieter gelten und für Anwendungsentwickler von Nutzen sein können.

Ein ähnlicher Mechanismus besteht darin, Objektdienste durch eine Erweiterung des SQL zu unterstützen. Anbieter relationaler Datenbanken und kleinere Objekte / relationale Anbieter sind Befürworter dieses Ansatzes. Es bleibt jedoch abzuwarten, wie erfolgreich diese Unternehmen bei der Gestaltung des Rahmens für die Objektspeicherung sein werden.

Die Frage bleibt jedoch: Ist die Objektpersistenz Teil des Objektverhaltens oder handelt es sich um einen externen Dienst, der Objekten über separate Schnittstellen angeboten wird? Wie wäre es mit Sammlungen von Objekten und Methoden zum Abfragen? Relationale, erweiterte relationale und objekt- / relationale Ansätze befürworten tendenziell eine Trennung zwischen Sprache, während Objektdatenbanken - und die Java-Sprache selbst - die Persistenz als der Sprache innewohnend betrachten.

Native Java-Persistenz durch Serialisierung

Die Objektserialisierung ist der sprachspezifische Java-Mechanismus zum Speichern und Abrufen von Java-Objekten und Grundelementen in Streams. Es ist anzumerken, dass es zwar seit einiger Zeit kommerzielle Bibliotheken von Drittanbietern zum Serialisieren von C ++ - Objekten gibt, C ++ jedoch nie einen nativen Mechanismus für die Serialisierung von Objekten angeboten hat. So verwenden Sie die Serialisierung von Java:

// "foo" in einen Stream schreiben (zum Beispiel eine Datei)

// Schritt 1. Erstellen Sie einen Ausgabestream

// Das heißt, erstellen Sie einen Bucket, um die Bytes zu empfangen

FileOutputStream out = neuer FileOutputStream ("fooFile");

// Schritt 2. Erstellen Sie ObjectOutputStream

// Das heißt, erstellen Sie einen Schlauch und stecken Sie den Kopf in den Eimer

ObjectOutputStream os = neuer ObjectOutputStream (out)

// Schritt 3. Schreiben Sie eine Zeichenfolge und ein Objekt in den Stream

// Das heißt, lassen Sie den Stream in den Eimer fließen

os.writeObject ("foo");

os.writeObject (neues Foo ());

// Schritt 4. Leeren Sie die Daten an ihr Ziel

os.flush ();

Die WriteobjectMethode serialisiert foo und seinen transitiven Abschluss - dh alle Objekte, auf die innerhalb des Diagramms von foo aus verwiesen werden kann. Innerhalb des Streams ist nur eine Kopie des serialisierten Objekts vorhanden. Andere Verweise auf die Objekte werden als Objekthandles gespeichert, um Platz zu sparen und Zirkelverweise zu vermeiden. Das serialisierte Objekt beginnt mit der Klasse, gefolgt von den Feldern jeder Klasse in der Vererbungshierarchie.

// Ein Objekt aus einem Stream lesen

// Schritt 1. Erstellen Sie einen Eingabestream

FileInputStream in = neuer FileInputStream ("fooFile");

// Schritt 2. Erstellen Sie einen Objekteingabestream

ObjectInputStream ins = new ObjectInputStream (in);

// Schritt 3. Ich muss wissen, was du liest

String fooString = (String) ins.readObject ();

Foo foo = (Foo) s.readObject ();

Objektserialisierung und Sicherheit

Standardmäßig schreibt und liest die Serialisierung nicht statische und nicht transiente Felder aus dem Stream. Diese Eigenschaft kann als Sicherheitsmechanismus verwendet werden, indem Felder deklariert werden, die möglicherweise nicht als private Transienten serialisiert werden. Wenn eine Klasse möglicherweise überhaupt nicht serialisiert wird writeObjectund readObjectMethoden zum Werfen implementiert werden sollten NoAccessException.

Persistenz mit Transaktionsintegrität: Einführung von JDBC

Die Java-Datenbankkonnektivität (JDBC) basiert auf der SQL-CLI (Client Level Interface) von X / Open und den ODBC-Abstraktionen von Microsoft und zielt darauf ab, einen Datenbankkonnektivitätsmechanismus bereitzustellen, der unabhängig vom zugrunde liegenden Datenbankverwaltungssystem (DBMS) ist. Um JDBC-kompatible Treiber zu werden müssen mindestens die ANSI SQL-2-Einstiegs-API unterstützen, die Drittanbieter von Tools und Anwendungen genügend Flexibilität für den Datenbankzugriff bietet.

JDBC ist so konzipiert, dass es mit dem Rest des Java-Systems konsistent ist. Anbietern wird empfohlen, eine API zu schreiben, die stärker typisiert ist als ODBC, was eine bessere statische Typprüfung beim Kompilieren ermöglicht.

Hier ist eine Beschreibung der wichtigsten JDBC-Schnittstellen:

  • java.sql.Driver.Manager übernimmt das Laden von Treibern und bietet Unterstützung für neue Datenbankverbindungen.

  • java.sql.Connection stellt eine Verbindung zu einer bestimmten Datenbank dar.

  • java.sql.Statement fungiert als Container zum Ausführen einer SQL-Anweisung für eine bestimmte Verbindung.

  • java.sql.ResultSet steuert den Zugriff auf die Ergebnismenge.

Sie können einen JDBC-Treiber auf verschiedene Arten implementieren. Am einfachsten wäre es, den Treiber als Brücke zu ODBC zu erstellen. Dieser Ansatz eignet sich am besten für Tools und Anwendungen, die keine hohe Leistung erfordern. Ein erweiterbareres Entwurf würde eine zusätzliche Indirektionsebene für den DBMS-Server einführen, indem ein JDBC-Netzwerktreiber bereitgestellt wird, der über ein veröffentlichtes Protokoll auf den DBMS-Server zugreift. Der effizienteste Treiber würde jedoch direkt auf die proprietäre DBMS-API zugreifen.

Objektdatenbanken und Java-Persistenz

Eine Reihe von laufenden Projekten in der Branche bieten Java-Persistenz auf Objektebene. Zum jetzigen Zeitpunkt sind PSE (Persistent Storage Engine) und PSE Pro von Object Design die einzigen vollständig Java-basierten, objektorientierten Datenbankpakete, die verfügbar sind (zumindest ist mir dies bekannt). Weitere Informationen zu PSE und PSE Pro finden Sie im Abschnitt Ressourcen.

Die Java-Entwicklung hat zu einer Abkehr vom traditionellen Entwicklungsparadigma für Softwareanbieter geführt, insbesondere in der Zeitachse des Entwicklungsprozesses. Beispielsweise werden PSE und PSE Pro in einer heterogenen Umgebung entwickelt. Und da es im Entwicklungsprozess keinen Verknüpfungsschritt gibt, konnten Entwickler verschiedene Funktionskomponenten unabhängig voneinander erstellen, was zu einem besseren und zuverlässigeren objektorientierten Code führt.

PSE Pro kann eine beschädigte Datenbank aus einer abgebrochenen Transaktion wiederherstellen, die durch einen Systemausfall verursacht wurde. Die Klassen, die für diese zusätzliche Funktionalität verantwortlich sind, sind in der PSE-Version nicht vorhanden. Es bestehen keine weiteren Unterschiede zwischen den beiden Produkten. Diese Produkte werden als "Dribbleware" bezeichnet - Softwareversionen, die ihre Funktionalität durch das Einstecken neuer Komponenten verbessern. In nicht allzu ferner Zukunft würde das Konzept des Kaufs großer, monolithischer Software der Vergangenheit angehören. Die neue Geschäftsumgebung im Cyberspace ermöglicht es Benutzern zusammen mit Java Computing, nur die Teile des Objektmodells (Objektdiagramm) zu erwerben, die sie benötigen, was zu kompakteren Endprodukten führt.

PSE verarbeitet Klassendateien nach, nachdem sie vom Entwickler erstellt wurden. Aus Sicht von PSE sind Klassen in einem Objektdiagramm entweder persistent-fähig oder persistent-fähig. Persistent-fähige Klassen können sich selbst beibehalten, während persistent-fähige Klassen persistente Objekte bearbeiten können. Diese Unterscheidung ist erforderlich, da die Persistenz für bestimmte Klassen möglicherweise nicht erwünscht ist. Der Postprozessor für Klassendateien nimmt die folgenden Änderungen an Klassen vor: