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.
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.
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.
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. SPs (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.
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.
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 :
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.
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.
Die Drei - Schichtenarchitektur hat in diesem Kontext folgende Vorteile :
Die klassischen Aufgaben eines RDBMS werden weiterhin genutzt :
Hieraus ergeben sich folgende Aufgaben :
erweitern ließen sich diese Aufgaben durch :
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 :
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
- Beispiel :
Im ERM sind zwei Tabellen definiert : Rechnungskopf und Rechnungsposition.
In beiden Tabellen existiert das Attribut OID vom Typ Objektid (Domänen Definition).
Schreiben wir nun eine Rechnung :
Rechnungskopf OID Name Rechnungsnummer
O120 Müller GmbH 1999-11-123
Rechnungsposition OID Produkt EZ - Preis Rechnung
O110 Messer 7.50 O120
O111 Schere 2.50 O120
O114 Licht 3.50 O120
- Generierung und Verwaltung des Surrogat durch die Datenbank selbst.
- Die OID ist während der Lebenszeit des OBJEKTES ( unabhängig von den Attributswerten )
konstant
- OID ist unabhängig vom Objekttyp
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.
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)
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.
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.
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.
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.
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.
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.