Session D-DBMS

Objektorientierte Zugriffsschicht 
zu RDBMS

George Emrich
KHEOPS GmbH


Objektorientierte Zugriffsschicht auf RDMS – Design / Umsetzung

Hinter allen in VFP realisierten Anwendungen stehen relationalen Datenbanken (VFP, SQLServer, etc.) . Anders ausgedrückt muß man aus einer objektorientierten ‘Welt’ auf Relationale Datenbanken (RDBMS) zugreifen. Dadurch entsteht eine syntaktische/semantische Lücke, die durch Verwendung von objektorientierten Datenbanken (ODBMS) geschlossen werden könnte. Dies stellt aus technischen und praktischen Gründen im Moment keine Alternative dar.

Die hier vorgestellte Alternative stellt sich als objektorientierte Zugiffs - Schicht dar. In der Session werden die Realisierungskonzepte vorgestellt und beispielhaft erläutert.

Wozu der Aufwand ?

Diese Frage ist kontraproduktiv. Sie wurde z.B. auch bei der Umstellung von FoxPro 2.6 nach VFP 3.0 gestellt

„Objektorientierte Programmierung, wozu ?“...

Der Folgende Text KANN NICHT auf alle Fragen eine Antwort geben, dazu ist das Thema zu komplex . Er soll im wesentlichen Konzepte andenken und die Unterschiede zwischen ODBMS und RDBMS aufzeigen und damit eine Motivation zum Umdenken liefern.

Der Vortrag setzt Wissen über RDBMS voraus.

Konzeptvergleich relationale vs. objektorientierte Datenbanken

Persistent & transient

Ziel der objektorientierten Welt ist es persistente, und transiente Objekte völlig gleichwertig zu behandeln.

Die Lebensdauer aller innerhalb einer Applikation definierten Daten endet maximal mit dem Laufzeitende, diese Daten heißen transient. Das Speichern dieser Daten in eine Datenbank macht die Daten dauerhaft, oder persistent. Um den Datenstatus von transient in persistent zu ändern muß, bei Verwendung von relationalen Datenbanken ein Programm zur Zustandsveränderung geschrieben werden. Die hierfür benötigte Sprache ist NICHT Visual Foxpro, sondern SQL.

In einer rein objektorientierten Welt sollte der Unterschied in der BEHANDLUNG von transienten und persistenten Objekten aufgelöst sein. Alle überführenden Operationen (z.B. Kopieren, umwandeln etc. ) sollten entfallen. Eine der wesentlichen Aufgaben bei der Programmierung einer Speicheroperation ist die Typentransformation. Der Fuchs hat bedeutend weniger elementare Datentypen als z.B. der SQL Server. Um einen Wert nun von einem Foxpro - Elementartyp in einen SQL - Elementartyp umzuwandeln muß ein Transformationsprogramm geschrieben werden.

Objektspeicherung vs. „Attributspeicherung“

Alle relationalen DBS speichern Daten in Tabellen. Grundsätzlich sind aber keine Operationen (Methoden) direkt auf Tabellenebene realisiert, sondern ausschließlich auf Ebene des RDMS selbst. SP’s (stored procedures oder Trigger ) sind auch semantisch nicht an Datenobjekte gebunden, sondern werden auf Ebene des RDMS ausgeführt.

Objektorientierte Systeme definieren Objekte durch Eigenschaften und auf diesen definierten Operationen.

Am einfachsten wäre es, Attribute und Operationen direkt auf der Datenbank zu definieren, um die künstliche Trennung zwischen Attributen und Operationen aufzuheben.

Wertidentität & Objektidentität

RDMS identifizieren alle Ihre Daten schlüsselbasiert. Die Entitätsintegrität wird durch Verwendung von Primärschlüsseln (natural, surrogat ) aufrechterhalten, die refferentielle Integrität wird durch Fremdschlüssel und zusätzliche Tabellen aufrechterhalten. Bei objektorientierten Datenbanken hat jedes Objekt eine Identität, die unabhängig von den Attributswerten (und damit von den Entitäten selbst) ist. Die RI wird durch Objektbeziehungen ersetzt.

Einfache Objekte & komplexe Objekte

SQL 92 gibt folgende Attributtypen vor :

Zusätzlich zu diesem Standard sind von den verschiedenen Herstellern von RDM Systemen noch andere Datentypen eingeführt worden, wie z.B. „binary large objects“, die es Datenbanken ermöglicht auch Bilder und Sprache zu sichern.

Aufgrund der schlichten Tatsache, daß alle Daten in relationalen Datenbanken in Form von Tabellen abgelegt werden müssen, sind alle Spalten gleich zu behandeln - es wird sozusagen die „Tabellenbreite“ vorgegeben.

Die Normalisierung eines ERM führt IMMER dazu, daß logisch ( und natürlich ) zusammenhängende Daten auseinandergerissen und in unterschiedlichen Tabellen abgespeichert werden. Eine Zusammenführung erfolgt immer über einen <SQL_SELECT>. In einem relational konzipierten Einwohnermelde - System ist es beispielsweise nötig, die Adresse vollständig zu normalisieren, um beispielsweise sicher herausfinden zu können, wer in einem Haus wohnt.

In ODMG 93 werden daher folgende komplexe Datentypen definiert um die „Zerstückelung“ logisch zusammengehöriger Daten zu verhindern :

Programmiersprache

Bei relationalen Datenbanksystemen existiert keine Anbindung an die verwendete Programmiersprache. Das heißt nicht, daß z.B. Foxpro nicht NATIV Datenoperationen (INSERT,DELETE etc. ) zur Verfügung stellt, sondern daß diese verwendeten Befehle deklarativ und nicht objektorientiert sind. Will man beispielsweise stored proc. Schreiben, so findet man sich in einer anderen ( nicht objektorientierten ) Sprache wieder, nämlich Transact SQL.

Die Nachteile eines solchen Konzeptes sind leicht zu definieren :

Deklarative Sprachsysteme eignen sich hervorragend für Datenbankabfragen, aber so gut wie gar nicht zur Anwendungsentwicklung. Einige Hersteller (SQL - Server) versuchen eigene Dialekte einzuführen, davon ist aber wegen mangelnder Kompatibilität abzuraten.

Realisierungskonzept

Die Zugriffsschicht ist als 3tier Anwendung realisiert, wobei die erste Schicht hierbei die objektorientierte Anwendung, die zweite besagte Zugriffsschicht und die dritte das RDMS darstellt.

Vorteile 3-Schichtenarchitektur

Die Drei - Schichtenarchitektur hat in diesem Kontext folgende Vorteile :

Nutzung klassischer RDBMS-Aufgaben

Die klassischen Aufgaben eines RDBMS werden weiterhin genutzt :

Aufgaben

Hieraus ergeben sich folgende Aufgaben :

erweitern ließen sich diese Aufgaben durch :

Zusätzliche Aufgaben einer objektorientierten Zugriffschicht :

Die objektorientierte Zugriffsschicht kümmert sich insbesondere um die Abbildung der Objekte in Tabellen. Sie kümmert sich um die Objektidentität und optimistische Transaktionslogik.

Für jeden Objekttyp existieren innerhalb der Zugriffsschicht Methoden zum Erzeugen, Lesen etc. innerhalb der Datenbank. Die zusätzlichen objektorientierten Aufgaben lassen sich zusammenfassen in :

  1. Objektidentität
  2. Transparenz
  3. Komplexe Typen
  4. Objektbeziehungen und -referenzen
  5. Vererbung
  6. Polymorphismus

1. Objektidentität (OID)

Es existiert eine strenge Trennung zwischen dem abgebildeten Objekt und dessen Werten. Jedes Objekt besitzt während seines „Livecycles“ eine es SYSTEMWEIT eineindeutig identifizierende IDENTITÄT unabhängig von allen Attributswerten. Objektidentität und Objektgleichheit sind hierbei unterschiedliche Konzepte.

Im relationalen System wird dies durch Einführung eines Surrogate abgebildet, der folgende Eigenschaft besitzt:

Systemweite Eindeutigkeit und Typ – Konstanz

Rechnungsposition OID Produkt EZ - Preis Rechnung

Um dies umsetzen zu können benötigen wir einen Surrogat - Verwalter. Die unterschiedlichen RDMS stellen zwar Identity - Inserts zur Verfügung, diese beziehen sich aber ausschließlich auf Tabellenebene und ermöglichen keinen systemintegrierten, global eindeutigen Bezeichner.

1.1 Surrogat - Verwalter

Aus Gründen der Portabilität ist die Verwendung eines RDM Hersteller spezifischen Mechanismus ausgeschlossen. Wir greifen daher auf einen eigenen Mechanismus zurück. Wir haben uns hierbei für ein transientes Objekt entschieden. Dieses Objekt ruft eine Stored Proc auf dem Server auf, die eine Systemweit eindeutige Nummer zurückgeliefert.

Um zu verhindern, daß parallel laufende Client - Prozesse einen Konflikt erzeugen wird die Surrogat- Tabelle auf einer gesonderten Systemdatenbank vorgehalten und in einer kurzen Transaktion angesprochen, die unabhängig von der Anwendung läuft.

Daraus folgt, daß der Surrogat systemweit nicht fortlaufend sein muß, bzw. ist. (siehe „Transparenz“)

2. Transparenz

Jedes Objekt erhält mit seiner Erzeugung unabhängig vom Zustand transient oder persistent eine OID. Ein Eintrag erfolgt zunächst in die Objektinstanztabelle mit dem Status „Neu“. Da ein solches Objekt verworfen werden kann ( einfach nicht abgespeichert wird ) ist damit auch die Surrogatfolge nicht zwangsläufig fortlaufend.

Wird innerhalb einer Transaktion ein Objekt mehrfach von der Datenbank gelesen, so entstehen mehrere Instanzen des Objektes. Die Objekteindeutigkeit wird dadurch erheblich verletzt. Um dies auszuschließen führen wir eine (lokale) Objektinstanztabelle ein. Diese Tabelle wirkt faktisch wie ein „Objektcache“.

3. Optimistische Transaktionslogik

Datenbanken arbeiten normalerweise mit einem pessimistischen Sperrverfahren, bei dem alle betroffenen Datensätze vor dem Schreibzugriff gesperrt und erst bei Transaktionsende freigegeben werden.

Eine optimistische Transaktionslogik geht davon aus, daß ein Satz nicht vor der Änderung, sondern erst am Transaktionsende auf der Datenbank gesperrt wird. Es wird ( z.B. durch einen Zeitstempel ) überprüft, ob eine konkurrierende Transaktion den Satz mittlerweile verändert hat. Im Konfliktfall wird die gesamte Transaktion verworfen ( ROLLBACK). Vorteil ist hierbei, daß die Menge der Satzsperren deutlich reduziert wird, andererseits aber die Mehrbelastung durch Rollbacks stark erhöht wird.

Die optimistische Transaktionslogik wurde folgendermaßen umgesetzt :

In einer kurzen Lesetransaktion werden alle Objekte von der Datenbank gelesen. Danach erfolgt die Bearbeitung der Objekte im volatilen Speicher, dabei wird eine Änderung NICHT direkt auf die Datenbank zurückgeschrieben.

Die so eingelesenen Objekte erhalten innerhalb der Objektinstanztabelle folgende Statuseinträge :

Falls man alle Eingaben persistent machen möchte erfolgt eine kurze Schreibtransaktion.

4. Objekt -> Relation

Im einfachsten Fall wird eine Klasse mit einer OID in eine Tabellenspalte abgebildet. Sind die Datentypen eines Objektes aber nicht mehr elementar so ist die Abbildung nicht trivial.

Wir können folgende Fälle unterscheiden:

4.1. Alle Objekteigenschaften lassen sich in eine Tabellenspalte gleichen Datentyps abbilden.

Jeder Foxpro - Datentyp wird in einen SQL - Datentyp konvertiert.

Bei INSERT wird automatisch ein SURROGAT vergeben.

4.2. Listentypen: Alle Listen werden in separaten Tabellen abgebildet. Pro persistenter Liste existiert eine Tabelle in der Datenbank.

5. Vererbung

Um Vererbung auf eine relationalen Datenbank aufzusetzen existieren letztlich zwei Möglichkeiten :

5.1. Jedes Objekt wird durch genau eine Tabellenspalte beschrieben. Jede Tabelle entspricht dann genau einem Objekttyp. Leider ergibt sich durch diese Lösung ein sehr umfangreiches Datenbankschema. Außerdem geraten wir in Konflikt mit z.B. Normalisierungsregeln.

5.2. Zu jedem Objekttyp gehört eine Tabelle, die nur diejenigen Attribute die im Supertyp nicht enthalten sind. Jedes Objekt ist dann folglich eine Relation seiner Tabelle mit den für diese Tabelle definierten Supertyp - Tabellen.

Dieses Prinzip geht zulasten der Performance.

6. Polymorphie

Erfolgt eine Lesezugriff auf Supertyptabellen (-> Supertype - entities) müssen alle dem Suchkriterium entsprechenden Superobjekte ausgegeben werden. Folglich müssen Objekte in allen Tabellen der Objekthierarchie gesucht werden.

Dies macht die Einführung einer Objekthierarchietabelle nötig.

 

Schlußbetrachtung

Wozu der Aufwand ? ...

Klar ist, daß ein solcher Aufwand für kleine Anwendungen übertrieben ist. So ist diese Zugriffsschicht letztlich durch verschiedene Großprojekte entstanden und durch deren Anforderungen umgestaltet und weiterentwickelt worden. Insbesondere die Einführung der Polymorphie und Vererbung ist für die meisten Anwendungen nicht notwendig.