Objektorientierte PL/SQL Programmierung

Sonntag, 2. November 2008

TYPE_OBJECT

Vollständige Source-Code für TYPE_OBJECT:


CREATE OR REPLACE TYPE TYPE_OBJECT AUTHID CURRENT_USER AS OBJECT
(
-- Attributes
object_type_name VARCHAR2(100)
-- Member functions and procedures
, MEMBER PROCEDURE DBMS_OUTPUT
, MEMBER FUNCTION TO_STRING RETURN VARCHAR2
, MEMBER FUNCTION TO_CLOB RETURN CLOB
, 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

/
CREATE OR REPLACE TYPE BODY TYPE_OBJECT
IS
MEMBER PROCEDURE DBMS_OUTPUT
IS
BEGIN
SYS.DBMS_OUTPUT.PUT_LINE(SELF.TO_STRING());
END;
------------------------------------------
MEMBER FUNCTION TO_STRING RETURN VARCHAR2
IS
BEGIN
RETURN SYS.XMLTYPE(SELF).extract('/').getStringVal() ;
EXCEPTION
WHEN OTHERS THEN
RETURN 'ERROR : ' SQLERRM;
END;
------------------------------------------
MEMBER FUNCTION TO_CLOB RETURN CLOB
IS
BEGIN
RETURN SYS.XMLTYPE(SELF).extract('/').getCLOBVal() ;
EXCEPTION
WHEN OTHERS THEN
RETURN 'ERROR : ' SQLERRM;
END;

------------------------------------------
MEMBER FUNCTION compare(in_type1 TYPE_OBJECT, in_type2 TYPE_OBJECT) RETURN INTEGER
IS
BEGIN
RETURN 1; -- default always not equal
END;
------------------------------------------
ORDER MEMBER FUNCTION compare2(in_other TYPE_OBJECT) RETURN INTEGER
IS
BEGIN
RETURN SELF.compare(SELF,in_other);
END;

END;
/

Merkmale:


  • AUTHID CURRENT_USER ist notwendig, um das TYPE_OBJECT auch von anderen Schemata vererben zu können. Damit haben alle abgeleiteten Types auch die AUTHID CURRENT_USER Berechtigung vererbt.

  • Attribut object_type_name ist notwendig, weil Oracle kein Interface kennt und für die Type-Deklaration mindestens ein Attribut vorhanden sein muss.

  • SYS.XMLTYPE(SELF).extract('/') repräsentiert einzelne Attribute in XML-Format mit Zeilenumbruch. Für die Types, die XML-Type- oder XML-String-Attribute beinhalten, können die TO_STRING und TO_CLOB Methoden auf eigene Art überschrieben werden, um den Inhalt richtig zu repräsentieren.

  • Da sich die ORDER- oder MAP-Methode des Types nicht überschreiben läßt, wird für Vergleichszwecke eine COMPARE-Methode definiert, die default immer "ungleich" liefert.
    Im Typevergleich-Bedarfsfall muß man die COMPARE-Methode in den vererbten Type überschreiben.

  • Für das Ersetzen des TYPE_OBJECT muß man diesen erst mit DROP TYPE TYPE_OBJECT FORCE; löschen und im Anschuß die invaliden Objekte kompilieren.

Sonntag, 5. Oktober 2008

Objektorientierte ROWTYPE


Einleitung

Für den traditionellen Einsatz der objektorientierte PL/SQL-Programmierung bietet Oracle ein ODBMS. Bei diesem Ansatz werden erst die Oracle Object Types definiert, auf denen objektorientierte Tabellen erzeugt werden. Für die Datennormalisierung verwendet man dort die OROWID. Um unter der Verwendung von OROWID den Inhalt des referenzierten Objektes zu bekommen oder eine Methode aufzurufen, verwendet man eine DEREF – Funktion. Für die Datenmanipulation sind wie früher die üblichen DML-Anweisungen verfügbar.







Im Gegensatz zur OO-PL/SQL-Programmierung für ODBMS, werden die hier vorgestellten Objekt Typen auf der Basis eines bestehenden relationalen DB-Models gebaut. Damit ist ein objektorientierter Zugriff auf die Tabelleninhalte und auf die in PL/SQL realisierten Prozeduren möglich. Die erstellten Object Types sind ein Analogon zum Oracle %ROWTYPE. Sie sind zusätzlichen mit Konstruktoren und Methoden ausgestattet, die den üblichen DML-Anweisungen wie SELECT, INSERT, UPDATE, DELETE, bzw. MERGE entsprechen. Somit werden die Datenmanipulationen vereinfacht. Es stehen Konstruktoren für Primary Key und Unique Key zur Verfügung, um basierend auf einer Id des RDBMS einen referenzierten Datensatz zu erhalten. Die TO_STRING – Methode gibt die Representation des Types aus. Die Ausgabe von TO_STRING erfolgt über die DBMS_OUTPUT Methode.

Mit den genannten Funktionalitäten können die Daten wie üblich auf der Basis eines relationalen Datenbankmodells in dem RDBMS gespeichert werden. Über das virtuelle objektorientierte Modell (OO-Schicht) sind die Zugriffe auf Tabellenspalten und die Ausführung der sämtlichen DML-Anweisungen vereinfacht.

Auf Grund dieser OO-ROWTYPEs kann man mittels der Vererbung und der Komposition, die so genannten Business-Objekte (BO) aufbauen, die die reale Welt abbilden. Diese BO-Types können durch Ableitung und Kombination von mehreren OO-ROWTYPEs realisiert werden.

Realisierung

1. Einführung des Object Type TYPE_OBJECT

Oracle gibt keinerlei Ursprungs Objekthierarchie vor, d.h. es gibt kein Objekt, der Basisklasse für alle abgeleiteten Object Types ist. Daher wird zunächst ein TYPE_OBJECT erstellt:

CREATE OR REPLACE TYPE SCOTT.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 TO_CLOB RETURN CLOB
, 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

Hiermit werden folgende Vorteile erzielt:
- Ein standardisiertes Interface für alle abgeleiteten Objekte, wie TO_STRING oder DBMS_OUTPUT;
- Einfache Übergabe abgeleiteter Typen als Parameter in beliebige Prozeduren oder Funktionen (Ursprungshierarchie);
- Definition der ORDER Funktion, die wegen der Überschreibung der Compare-Methode das Vergleich die Instanzen beliebigen Typen ermöglicht.


2. Einführung von OO-ROWTYPE

Für die SCOTT-Schema Tabellen EMP und DEPT hätten die oben genannten OO-ROWTYPE in der ODBMS folgende Umsetzung:

CREATE OR REPLACE TYPE SCOTT.ROW_DEPT UNDER SCOTT.TYPE_OBJECT(
-- attributes
deptno NUMBER(2)
, dname VARCHAR2(14)
, loc VARCHAR2(13)
-- define constructors
, CONSTRUCTOR FUNCTION ROW_DEPT RETURN SELF AS RESULT
, CONSTRUCTOR FUNCTION ROW_DEPT( in_deptno NUMBER, in_dname VARCHAR2
, in_loc VARCHAR2) RETURN SELF AS RESULT
, CONSTRUCTOR FUNCTION ROW_DEPT(in_deptno NUMBER) RETURN SELF AS RESULT
-- member functions
, MEMBER FUNCTION ROW_EXISTS(in_deptno NUMBER) RETURN BOOLEAN
, OVERRIDING MEMBER FUNCTION COMPARE( in_type1 GLOBAL.TYPE_OBJECT
, in_type2 GLOBAL.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_deptno NUMBER)
, MEMBER PROCEDURE ROW_LOCK
, MEMBER PROCEDURE ROW_LOCK(in_deptno NUMBER)
, MEMBER PROCEDURE ROW_DEFAULT
) NOT FINAL

CREATE OR REPLACE TYPE SCOTT.ROW_EMP UNDER SCOTT.TYPE_OBJECT(
-- attributes
empno NUMBER(4)
, ename VARCHAR2(10)
, job VARCHAR2(9)
, mgr NUMBER(4)
, hiredate DATE
, sal NUMBER(7,2)
, comm NUMBER(7,2)
, deptno NUMBER(2)
-- constructors
, CONSTRUCTOR FUNCTION ROW_EMP RETURN SELF AS RESULT
, CONSTRUCTOR FUNCTION ROW_EMP( in_empno NUMBER, in_ename VARCHAR2
, in_job VARCHAR2, in_mgr NUMBER
, in_hiredate DATE, in_sal NUMBER
, in_comm NUMBER, in_deptno NUMBER
) RETURN SELF AS RESULT
, CONSTRUCTOR FUNCTION ROW_EMP(in_empno NUMBER) RETURN SELF AS RESULT
-- member functions
, MEMBER FUNCTION ROW_EXISTS(in_empno NUMBER) RETURN BOOLEAN
, OVERRIDING MEMBER FUNCTION compare( in_type1 GLOBAL.TYPE_OBJECT
, in_type2 GLOBAL.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_empno NUMBER)
, MEMBER PROCEDURE ROW_LOCK
, MEMBER PROCEDURE ROW_LOCK(in_empno NUMBER)
, MEMBER PROCEDURE ROW_DEFAULT
) NOT FINAL

und die dazu gehörigen Container Typen und Packages:

CREATE OR REPLACE TYPE TABLE_EMP AS TABLE OF SCOTT.ROW_EMP;
CREATE OR REPLACE TYPE TABLE_DEPT AS TABLE OF SCOTT.ROW_DEPT;

CREATE OR REPLACE PACKAGE PA_DEPT IS
FUNCTION FU_SELECT RETURN TABLE_DEPT;
END;
CREATE OR REPLACE PACKAGE PA_EMP IS
FUNCTION FU_SELECT RETURN TABLE_EMP;
FUNCTION FS_DEPTNO(IN_DEPTNO IN EMP.DEPTNO%TYPE) RETURN TABLE_EMP;
FUNCTION FS_MGR(IN_MGR IN EMP.MGR%TYPE) RETURN TABLE_EMP;

END;

Man erkennt, dass die gebauten Typen vergleichbar zu den %ROWTYPE sind, aber weitere Funktionalitäten abbilden.
Zu diesen gehören :




  • Konstruktoren für Primary- und Unique Keys;
  • Funktionen:
    - ROW_EXIST - prüft ob die Datensatz mit angegeben Primary oder Unique Key in der Datenbank schon vorhanden ist;
    - COMPARE – vergleicht zweier Instanzen.
  • Prozeduren:
    - ROW_INSERT, ROW_UPDATE, ROW_DELETE, ROW_MERGE, ROW_SELECT – die entsprechende DML-Anweisungen ausüben;
    - ROW_DEFAULT – setzt aus der Tabellendefinition die Defaultwerte in die dazu gehörige Attribute;
    - ROW_SAVE – ersetzt die MERGE- Anweisung, und enthält im Gegensatz zum Oracle-MERGE eine RETURNING Clause;
    - ROW_LOCK - für die pessimistische Sperre des Tabellendatensatzes.

Zusätzlich wird für jeden OO-ROWTYPE ein Container Type und eine oder mehrere Data-Cartridges für die Foreign Keys und die gesamte Tabelle erzeugt. Mit Data-Cartridge ist eine Funktionen gemeint, welche die Container Tabelle zurück liefert. Leider erlaubt Oracle nicht, im Type eigene Container Types zu verwenden. Deswegen sind diese Data-Cartridges in einem seperaten Package hingelegt.

Die manuelle Erstellung die OO-ROWTYPEs ist sehr aufwendig und fehlerträchtig, hier hilft ein in Java selbstgeschriebener Generator, der die Oracle Dictionary Definitionen ausliest und diese Arbeit erledigt. Der Generator ist in das DBMS eingebunden und wird über ein Wrapper Skript aufgerufen.


3. Aufbau der Business Object Types (BO-TYPEs)


Auf der Basis des generierten Sources, der Vererbung und Komposition wird über so genannte Busines Objekte, der Aufbau der realen Welt abgebildet.

Dies ist im vorliegende Fall:



  • TYPE_MANAGER – ist vom ROW_EMP vererbt und beinhaltet alle untergeordneten Mitarbeiter, die er leitet.

    CREATE OR REPLACE TYPE TYPE_MANAGER UNDER ROW_EMP(
    -- attributes
    EMPLOYEES TABLE_EMP
    -- constructors
    , CONSTRUCTOR FUNCTION TYPE_MANAGER RETURN SELF AS RESULT
    , CONSTRUCTOR FUNCTION TYPE_MANAGER(IN_EMPNO NUMBER) RETURN SELF AS RESULT
    ) NOT FINAL

  • TYPE_DEPARTMENT – ist vom ROW_DEPT abgeleitet und beinhaltet alle Mitarbeiter die in der Abteilung tätig sind, so wie den leitenden Manager.

    CREATE OR REPLACE TYPE TYPE_DEPARTMENT UNDER ROW_DEPT(
    -- attributes
    EMPLOYEES TABLE_EMP
    -- constructors
    , CONSTRUCTOR FUNCTION TYPE_DEPARTMENT RETURN SELF AS RESULT
    , CONSTRUCTOR FUNCTION TYPE_DEPARTMENT(in_deptno NUMBER) RETURN SELF AS RESULT

    -- member functions
    , MEMBER FUNCTION GET_MANAGER RETURN TYPE_MANAGER
    ) NOT FINAL

  • TYPE_ENTERPRISE – ist vom TYPE_OBJECT vererbt und besteht aus Unternehmungsname, Vorstandvorsitzendem, Abteilungen und Mitarbeitern.

    CREATE OR REPLACE TYPE TYPE_ENTERPRISE UNDER TYPE_OBJECT(
    -- attributes
    NAME VARCHAR2(100)
    , PRESIDENT TYPE_MANAGER
    , DEPARTMENTS TABLE_DEPT
    , EMPLOYEES TABLE_EMP
    -- constructors
    , CONSTRUCTOR FUNCTION TYPE_ENTERPRISE RETURN SELF AS RESULT
    ) NOT FINAL

Die erzeugten Objekt-Typen und Container lassen sich einfach mit SELECT –Anweisungen aufrufen:
SQL> SELECT TYPE_DEPARTMENT(20) FROM DUAL;
TYPE_ENTERPRISE()(OBJECT_TYPE_NAME, DEPTNO, DNAME, LOC, NAME, PRESIDENT(OBJECT_T
--------------------------------------------------------------------------------
TYPE_DEPARTMENT('TYPE_DEPARTMENT', 20, 'RESEARCH', 'DALLAS',
TABLE_EMP(
ROW_EMP('ROW_EMP', 7369, 'SMITH', 'CLERK', 7902, '17.12.80', 800, NULL, 20),
ROW_EMP('ROW_EMP', 7566, 'JONES', 'MANAGER', 7839, '02.04.81', 2975, NULL, 20),
ROW_EMP('ROW_EMP', 7788, 'SCOTT', 'ANALYST', 7566, '19.04.87', 3000, NULL, 20),
ROW_EMP('ROW_EMP', 7876, 'ADAMS', 'CLERK', 7788, '23.05.87', 1100, NULL, 20),
ROW_EMP('ROW_EMP', 7902, 'FORD', 'ANALYST', 7566, '03.12.81', 3000, NULL, 20)
)
)

SQL> SELECT VALUE(e) FROM TABLE (TYPE_MANAGER(7698).EMPLOYEES) e;
VALUE(E)(OBJECT_TYPE_NAME, EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO)
--------------------------------------------------------------------------------
ROW_EMP('ROW_EMP', 7499, 'ALLEN', 'SALESMAN', 7698, '20.02.81', 1600, 300, 30)
ROW_EMP('ROW_EMP', 7521, 'WARD', 'SALESMAN', 7698, '22.02.81', 1250, 500, 30)
ROW_EMP('ROW_EMP', 7654, 'MARTIN', 'SALESMAN', 7698, '28.09.81', 1250, 1400, 30)
ROW_EMP('ROW_EMP', 7844, 'TURNER', 'SALESMAN', 7698, '08.09.81', 1500, 0, 30)
ROW_EMP('ROW_EMP', 7900, 'JAMES', 'CLERK', 7698, '03.12.81', 950, NULL, 30)

Oder mit PL/SQL-Skript:

DECLARE
e TYPE_ENTERPRISE := TYPE_ENTERPRISE();
BEGIN
e.dbms_output;
END;

Entsprechendes DBMS Output:

SCOTT.TYPE_ENTERPRISE
(
OBJECT_TYPE_NAME = TYPE_ENTERPRISE
NAME = King Corporation
PRESIDENT = SCOTT.TYPE_MANAGER
(
OBJECT_TYPE_NAME = TYPE_MANAGER
EMPNO = 7839
ENAME = KING
JOB = PRESIDENT
MGR =
HIREDATE = 17.11.1981 00:00:00
SAL = 5000
COMM =
DEPTNO = 10
EMPLOYEES = <>
)
DEPARTMENTS = <>
EMPLOYEES = <>
)


Die PL/SQL-Programmierung würde jetzt so ähnlich aussehen:

DECLARE
m TYPE_MANAGER;
BEGIN
m := TYPE_MANAGER(7839);
m.sal := m.sal * 2;
m.row_update;
m.dbms_output;
END;



Entsprechendes DBMS Output:

SCOTT.TYPE_MANAGER
(
OBJECT_TYPE_NAME = TYPE_MANAGER
EMPNO = 7839
ENAME = KING
JOB = PRESIDENT
MGR =
HIREDATE = 17.11.1981 00:00:00
SAL = 10000
COMM =
DEPTNO = 10
EMPLOYEES = <>
)



Hiermit gelingt es, eine virtuelle ODBMS auf dem Basis eines RDBMS aufzubauen und PL/SQL –Programmierung für RBDMS objectorientiert zu machen.

Das gebaute Model hat folgende Vorteile :

  • keine Funktionalitätsverluste gegeüber der bestehenden ODBMS Lösung von Oracle

  • Das Objektmodel entspricht dem relationalen Datenmodel und kann ohne Änderung am relationalen Datenmodel erzeugt und verwendet werden

  • Vereinfachung der DML-Anweisungen

  • Zugriffe auf manuell geschriebenen Business Objekte sind unabhängig von der unterliegenden Datenquelle

  • Auf die erstellten Objekte und deren Methoden lassen sich über Generatoren (wie z.B. JPublisher oder selbst geschriebene) die passenden Zugriffsklassen für z.B. Java erstellen

Zur Nachbesserung von Architekturnachteilen und Bugs, wären gewünscht:

  • die Oracle Object-Typen unterstützen nicht ROWID, %TYPE und INDEX BY TABLE

  • ein Type kann nicht Container von eigenen Instanzen referenzieren

  • die entsprechendes Gegenstück der z.B. in Java existierenden Interfaces fehlt (deswegen beinhaltet TYPE_OBJECT das überflüssige Attribute OBJECT_TYPE_NAME)

  • ein Type, der von anderen Type vererbt ist, oder von Container referenziert ist, kann mittels REPLACE nicht mehr ersetzt werden (nur ALTER TYPE Anweisung ist für solche Änderungen vorgesehen). Momentan ist nur ein Umweg über DROP TYPE force möglich.

Blog-Archiv