Objektorientierte PL/SQL- Programmierung Autoren: Andriy Terletskyy und Michael Meyer DOAGNews Q3_2004 Dieses Werk ist urheberrechtlich geschützt. Die dadurch begründeten Rechte, insbesondere die der Übersetzung, des Nachdrucks, des Vortrags, der Entnahme von Abbildungen und Tabellen, der Funksendung, der Mikroverfilmung oder der Vervielfältigung auf anderen Wegen und der Speicherung in Datenverarbeitungsanlagen, bleiben, bei auch nur auszugsweiser Verwertung, vorbehalten. Eine Vervielfältigung dieses Werkes oder von Teilen dieses Werkes ist auch im Einzelfall nur in den Grenzen der gesetzlichen Bestimmungen des Urheberrechtes der Bundesrepublik Deutschland vom 9. September 1965 in der jeweils geltenden Fassung zulässig. Sie ist grundsätzlich vergütungspflichtig. Zuwiderhandlungen unterliegen den Strafbestimmungen des Urheberrechtsgesetzes.
Die Oracle-Types sind relativ wenig bekannt und genutzt. Dabei bieten sie zahlreiche Möglichkeiten zur "OO"-artigen Softwareentwicklung in PL/SQL und Java. Im vorliegenden Artikel wird zunächst kurz auf grundlegende objekt-relationale-features in Oracle eingegangen, um dann einen Generator zur Erzeugung von Objekten zur einfachen Datenmanipulation vorzustellen. Der Generator erlaubt eine schnelle und verlässliche Erzeugung dieser Objekte, deren Nutzung die Programmentwicklung in PL/SQL vereinfacht. Die Sourcen können schneller erstellt werden und sind deutlich lesbarer als klassische prozedurale PL/SQL-Sourcen. Mit Version 8.1 wurden die objekt-relationalen-features Teil des Base Server Products.der Oracle Datenbank. Zuvor mussten sie noch separat erworben werden. Version 9i komplettiert die OO- Unterstützung der Datenbank, so dass nun u. a. folgende Features zur Verfügung stehen: Vererbung (Inheritance: Es gibt eine Einfachvererbung bei den Typen (Types analog zu Java. Types können als NOT FINAL gekennzeichnet werden und erlauben damit die Anlage von Untertypen, die von einem NOT FINAL-Vatertyp abgeleitet sind. Der Default für neue Objekte ist FINAL. Weiterhin können Objekte als NOT INSTANTIABLE gekennzeichnet werden. Diese Option ist sinnvoll für Objekte, die reine Supertypen darstellen sollen. So ist sichergestellt, dass die Methoden in den abgeleiteten Objekten ausprogrammiert werden. Untertypen können vorhandene Methoden überschreiben und neue Methoden und Attribute hinzufügen. Leider existiert keine Möglichkeit, eine überschriebene Methode im Vaterobjekt aufzurufen (analog super( in Java. Dynamisches Binden (Dynamic method dispatch, Dynamic binding oder Late Binding: Die Auswahl der auszuführenden Methode erfolgt zur Laufzeit und nicht zur Compilezeit. Änderungen von Objekten (Type Evolution: Nachträgliche Änderungen an Objekten können in begrenztem Maße vorgenommen werden. Attribute können hinzugefügt und entfernt werden. Außerdem kann die Länge oder Genauigkeit von Attributen vergrößert werden. Methoden können hinzugefügt oder entfernt werden. Bei der Durchführung von Objektänderungen (ALTER TYPE kann angegeben werden, ob die zugehörigen Tabellendaten sofort mit zu konvertieren sind, oder nicht (CASCADE NOT INCLUDING TABLE DATA. Objekte können auf Tabellenebene (Row Object oder auf Spaltenebene (Column Object benutzt werden. Die Methoden können in diversen Sprachen geschrieben werden, wobei PL/SQL- und in die Datenbank geladene Java-Methoden im Adressraum des Servers ablaufen. In C, C++ geschriebene Methoden laufen außerhalb des Datenbankservers ab. Wie kann man nun die Oracle OO-Verfahren für vorhandene relationalbasierte Datenbankmodellen nutzen? Die Daten liegen in traditionellen relationalen Tabellen vor. Dieses Modell wird mit den üblichen Tools erstellt. Der Zugriff erfolgt über PL/SQL und ist damit eher prozedural. Die Einführung von OO-Rowtypen zieht hier eine schlanke Zwischenschicht ein, so dass der Zugriff über OO-artige Methoden und Views erfolgen kann. Die folgende Abbildung zeigt, dass pro Tabelle ein Type erstellt wird. Die Attribute des Types entsprechen den Tabellenspalten analog dem %ROWTYPE. Die Methoden entsprechen den DML-Anweisungen.
Abbildung: Ergänzung RDBMS-Tabelle mit objektorientiertem Rowtype Oracle gibt keinerlei Objekthierarchie vor, d. h. es gibt kein Objekt analog zum Basistyp Object in Java, von dem alle anderen abgeleitet sind. Daher wird zunächst ein globales Vaterobjekt erstellt, von dem alle anderen Objekte abgeleitet werden (bei uns: TYPE_OBJECT. Dieses Vaterobjekt hat nur ein Attribute und wenige Methoden: CREATE OR REPLACE TYPE TYPE_OBJECT AS OBJECT ( -- Attributes object_type_name VARCHAR2(100 -- Member functions and procedures,member PROCEDURE DBMS_OUTPUT,MEMBER FUNCTION to_string RETURN VARCHAR2,MEMBER FUNCTION compare(in_type1 TYPE_OBJECT, in_type2 TYPE_OBJECT RETURN INTEGER,ORDER MEMBER FUNCTION compare2(in_other TYPE_OBJECT RETURN INTEGER NOT FINAL NOT INSTANTIABLE Oracle kennt leider keine Interfaces, daher wird beim TYPE_OBJECT das Attribute object_type_name definiert. Die Methoden bilden das Standardinterface für alle abgeleiteten Objekte: DBMS_OUTPUT gibt das Objekt selbst über SYS.DBMS_OUTPUT.PUT_LINE aus, to_string entspricht der tostring(-java-routine, compare-methode vergleicht zwei Instanzen des Types, compare2 ist die ORDER-Funktion, die von Oracle bei Vergleichen wie ORDER BY, GROUP BY, DISTINCT aufgerufen wird. Die ORDER MEMBER FUNCTION kann in abgeleiteten Objekten nicht überschrieben werden, daher wird die MEMBER FUNCTION compare überschrieben, die ihrerseits durch die ORDER MEMBER FUNCTION aufgerufen wird. Das TYPE_OBJEKT ist wegen der Anweisung NOT INSTANTIABLE selbst nicht instanzierbar. Erst abgeleitete Objekte, sofern sie selbst nicht wieder NOT INSTANTIABLE sind, können instanziert werden. Der object_type_name lässt sich gut mittels des Reflection-API ermitteln:
declare t_object type_object; BEGIN dbms_output.put_line('typename=' AnyData.ConvertObject(t_object.GetTypeName(; END; Ausgabe: Typename=TYPE_OBJECT Der hier kurz angesprochene ANYTYPE-Datentyp eignet sich gut für Tabellenspalten und als Parameter für Methoden, deren Datentyp zum Erstellungszeitpunkt des Codes noch nicht bekannt ist oder variabel sein soll. Dies ist vergleichbar zu einem void * unter C oder dem java.lang.object. TYPE_OBJECT bietet darüber hinaus ein Interface und kann abgeleitet werden. Hierzu wird für jede relationale Tabelle ein objekt-orientierter ROWTYPE, abgeleitet vom TYPE_OBJECT, erstellt, der zu den bestehenden Attributen (Tabellenspalten noch Methoden und Konstruktoren besitzt, die Datenmanipulationen (DML erlauben. Zu den Methoden gehören im einzelnen: ROW_SELECT, ROW_INSERT, ROW_UPDATE, ROW_MERGE, ROW_DELETE und ROW_SAVE. Es wird eine ROW_SELECT-Methode für jeden eindeutigen Schlüssel (PRIMARY KEY, UNIQUE KEY erstellt. ROW_EXISTS prüft, ob der entsprechende Datensatz mit dem übergebenen PRIMARY KEY oder UNIQUE KEY in der Tabelle vorhanden ist. ROW_DEFAULT setzt die Attribute auf die als Default-definierten Werte für Spalten in der Tabelle. ROW_INSERT und ROW_UPDATE verfügen über RETURNING-clauses, die die bestehenden Attribute des Types aktualisieren, falls sie von Triggern geändert werden. Leider besitzt die neu in Oracle 9i/10g hinzugefügte MERGE-Anweisung keine RETURNING clause, daher ergänzen wir diesen Leerraum mit der ROW_SAVE- Methode, die selbständig zwischen INSERT und UPDATE entscheidet und die Attribute des Objektes setzt. Zu den Konstruktoren gehören der Standard Konstruktor (ohne Attribute, der Vollkonstruktor (mit allen Attributen und Konstruktoren mit den vorhandenen PRIMARY KEY und UNIQUE KEYs. So zum Beispiel, für die Tabelle LAND CREATE TABLE LAND ( LAND_ID NUMBER(12 PRIMARY KEY, LAND_ISO CHAR(2 UNIQUE, LANDNAME VARCHAR2(35, WAEHRUNG CHAR(3, ANDERUNGSDATUM DATE DEFAULT SYSDATE NOT NULL
erzeugt folgenden Type: CREATE OR REPLACE TYPE ROW_LAND UNDER TYPE_OBJECT ( -- attributes LAND_ID NUMBER(12, LAND_ISO CHAR(2, LANDNAME VARCHAR2(35, WAEHRUNG CHAR(3, ANDERUNGSDATUM DATE -- constructors, CONSTRUCTOR FUNCTION ROW_LAND RETURN SELF AS RESULT, CONSTRUCTOR FUNCTION ROW_LAND(LAND_ID NUMBER, LAND_ISO CHAR, LANDNAME VARCHAR2, WAEHRUNG CHAR, ANDERUNGSDATUM DATE RETURN SELF AS RESULT, CONSTRUCTOR FUNCTION ROW_LAND(IN_LAND_ID NUMBER RETURN SELF AS RESULT, CONSTRUCTOR FUNCTION ROW_LAND(IN_LAND_ISO CHAR RETURN SELF AS RESULT -- member functions, MEMBER FUNCTION ROW_EXISTS(IN_LAND_ID NUMBER RETURN BOOLEAN, MEMBER FUNCTION ROW_EXISTS(IN_LAND_ISO CHAR RETURN BOOLEAN, OVERRIDING MEMBER FUNCTION TO_STRING RETURN VARCHAR2, OVERRIDING MEMBER FUNCTION COMPARE(in_type1 TYPE_OBJECT,in_type2 TYPE_OBJECT RETURN INTEGER -- member procedures, MEMBER PROCEDURE ROW_INSERT, MEMBER PROCEDURE ROW_UPDATE, MEMBER PROCEDURE ROW_MERGE, MEMBER PROCEDURE ROW_SAVE, MEMBER PROCEDURE ROW_DELETE, MEMBER PROCEDURE ROW_SELECT(IN_LAND_ID NUMBER, MEMBER PROCEDURE ROW_SELECT(IN_LAND_ISO CHAR, MEMBER PROCEDURE ROW_DEFAULT NOT FINAL Dieser wird in PL/SQL z. B. folgendermaßen benutzt: DECLARE land ROW_LAND; -- declarieren der Objektinstanz BEGIN -- Lesen einer Instanz aus der Datenbank mit UNIQUE KEY: 'DE' land := ROW_LAND('DE'; -- Instanz in DBMS_OUTPUT ausgeben land.dbms_output;
-- Attribute ändern land.waehrung := 'EUR'; -- speichern land.row_update; -- Instanz nochmals in DBMS_OUTPUT ausgeben land.dbms_output; -- erzeugen einer neuen Instanz land := ROW_LAND(72, 'UA', 'Ukraine', 'UAH', null; -- anlegen in der Datenbank land.row_insert; -- Instanz in DBMS_OUTPUT ausgeben land.dbms_output; END; Output: ROW_LAND(LAND_ID = 4 LAND_ISO = DE LANDNAME = Bundesrepublik Deutschland WAEHRUNG = DEM ANDERUNGSDATUM = 01.01.1970 00:00:00 ROW_LAND(LAND_ID = 4 LAND_ISO = DE LANDNAME = Bundesrepublik Deutschland WAEHRUNG = EUR ANDERUNGSDATUM = 01.01.2002 00:00:00 ROW_LAND(LAND_ID = 72 LAND_ISO = UA LANDNAME = Ukraine WAEHRUNG = UAH ANDERUNGSDATUM = 27.04.2004 18:28:16 Die OO-ROWTYPEs für alle bestehenden relationalen Tabellen manuell zu erstellen, ist sehr arbeitsaufwendig und fehlerträchtig. Dabei hilft uns ein selbst geschriebener PL/SQL- Sourcegenerator, der als Java-Klasse in der Datenbank liegt und für alle bzw. ausgewählte Tabellen die OO-ROWTYPEs generiert. Der PL/SQL-Aufruf der Generatormethode sieht folgendermaßen aus: begin development.pa_develop.makeobjectrowtype('land'; end;
Eine wünschenswerte Erweiterung des Generators stellt die automatische Neuanlage und Aktualisierung der OO-Typen nach DDL-Vorgängen dar. Ein DDL-Trigger erlaubt die erforderlichen Änderungen nicht unmittelbar. Es sollte aber möglich sein, mit Hilfe eines DDL-Triggers und der Nutzung der Advanced-Queueing-Möglichkeiten in Oracle dies zu erreichen. Mit Hilfe des Generators lassen sich OO-Typen also einfach und fehlerfrei erstellen. Programme, die diese OO-Typen benutzen sind kurz, man kann sie leicht lesen und verstehen. Sinnvoll ist die Nutzung der OO-Typen auf Einzelsatzebene. Bei der Verarbeitung vieler Datensätze z. B. im Batch sind speziell optimierte SQL-Statements besser geeignet. Weiterhin ist zu bedenken, dass die Objekttypen Anforderungen stellen, die u. U. über die traditionellen Kenntnisse von Datenbankentwicklern hinausgehen. Grundlegende Kenntnisse in objekt-orientierter Systementwicklung und vertieftes Wissen um deren spezielle Umsetzung in der Datenbank sind hier gefragt. Kontakt: Andriy Terletskyy, Berenberg Bank, Joh. Berenberg, Gossler & Co. KG, Hamburg andriy.terletskyy@berenbergbank.de Michael Meyer, TietoEnator Consulting GmbH, Hamburg michael.meyer@tietoenator.com