Oracle APEX 19 Tutorial Teil 5 – Process and Condition

Dieser Teil des Oracle APEX Tutorials zeigt Ihnen, wie Sie Prozesse und Bedingungen ( Conditions ) in Ihren Anwendungen nutzen können.

Es ist ein wenig Zeit vergangen, seid dem ich den Teil 4 des APEX Einsteiger-Tutorials verfasst habe.  Die bisherigen Beschreibungen basierten noch auf der APEX Version 18.2. Diesem Artikel liegt nun die Version 19.1 zu Grunde, so dass Sie sich direkt mit dieser APEX Version vertraut machen können.

Noch einmal zur Erinnerung. Im letzten Artikel (Teil 4)  habe ich beschrieben, wie Sie für eine Tabelle eine Übersichtsseite und eine Form zum Erfassen und Bearbeiten des Tabelleninhaltes erstellen können.

Abbildung 34: APEX Form
Abbildung 34: APEX Form

Die Abbildung 34 zeigt Ihnen den Ausgangspunkt für dieses Tutorial. Für jede Spalten der Tabelle (NAMEN) wurde in der erstellen Form ein Feld – in APEX Item genannt – eingefügt. Je nach Spaltentyp wurden verschiedene Item-Typen verwendet.

Bei den letzten vier Items der Seite handelt es sich um Audit-Spalten in denen der Ersteller, das Erstellungsdatum sowie den letzten Änderer inkl. Änderungszeitpunkt erfasst werden sollen. 

Life-Cycle

Um zu verstehen, wie und wann ein sogenannter Process in einer APEX-Seite ausgeführt wird, ist es sinvoll sich zu vergegenwärtigen, wie eine Seite erzeugt wird.

Initial wird eine Seite durch einen Seitenaufruf per URL (http get) vom APEX-Web-Server angefordert. Der Web-Server reicht den Aufruf an die Datenbank weiter und führt dabei die PL/SQL Funktion f aus. Die Parameter, die der URL respektive dem http get request mitgegenen wurden, werden als Parameter an f  übergeben. In Abhängigkeit des aktuellen Session-Zustands, welcher im sogenannten Session-Cache gehalten wird, und den übergebenen Parametern, rendert f die neuen HTML-Seite. Diese wird dann als Antwort an den Client zurück geschickt. In anderen Szenarien erfolgt die Anfrage (Requests) mittels http-put, wobei sich der Ablauf aber ähnelt.

Im Rahmen dieses klassischen Anfrage- Antwortzyclusses ermöglicht es APEX Ihnen zu verschiedenen Zeitpunkten sogenannte Processes in der Datenbank auszuführen.

Prozess

Es gibt verschiedene Typen von Prozessen. Sie können z.B. vorgefertigte Prozesse verwenden, um Date aus einer Form in die Tabellen der Datenbank zu übertragen. Sie können eigene PL/SQL – Prozesse erstellen oder auch Web Services aufrufen, Mails versenden und vieles mehr.

Für dieses Beispiel sollen die Audit-Felder automatisch mit den entsprechenden Werten versehen werden.  

PL/SQL-Process erstellen

Eine oft genutzte Variante sind Prozesse, die Sie in PL/SQL erstellen können. Um so einen Prozess anzulegen gehen Sie wie folgt beschrieben vor.

Ausgehend von der Seite 3, welche in Abbildung 34 zu sehen ist, klicken Sie in der Developer Toolbar auf den Button “Edit Page”. 

Abbildung 35: APEX Create process
Abbildung 35: APEX Create process

Im Page Designer finden Sie, wie in Abbildung 35 zu sehen ist, die Möglichkeit einen neuen Prozess zu erstellen. Das geht z.B. über das Kontextmenü des Knotens process.

Geben Sie dem Prozess über seine Eigenschaften einen Namen (ID) und wählen sie als Typ PL/PQL-Code. Im Bereich source können Sie nun Ihren Quellcode unterbringen. 

In diesem Beispiel sollen das Item P3_CHANGED_BY und P3_CHANGED_ON mit einem Wert versehen werden.

Um auf ein Item zuzugreifen, stellen Sie dem Item-Namen einfach ein : voran. So können Sie z.B. über :P3_CHANGED_BY := :APP_USER;  den Namen des angemeldeten Users dem Item zuweisen. Abbildung 36 zeigt den kompletten Quellcode für dieses Beispiel.

Abbildung 36: PL/SQL Process
Abbildung 36: PL/SQL Process

Wie Sie dort sehen können, wird über sysdate noch der aktuelle Zeitpunkt einer Änderung ermittelt. Dieser wird allerdings als Char entsprechend formatiert dem Item zugewiesen. Achten Sie darauf, dass die Formatmaske hier mit der des Items übereinstimmt.

Abbildung 37: Formatmask
Abbildung 37: Formatmask

Anschließend bietet es sich noch an, die beiden Items in der Seite 3 auszublenden.

Abbildung 38: Hidden
Abbildung 38: Hidden

Das können Sie recht einfach bewerkstelligen. Markieren Sie dazu im Page Designer die beiden Items un d ändern Sie den Item-Typ auf Hidden (Ausgeblendet).

Wenn Sie nun über die Seite 3 einen neuen Eintrag erfassen oder einen vorhandenen Eintrag ändern, wird der Zeitpunkt der Änderung und der Name des Users vermerkt.

Conditions – Bedingte Prozesse

Wie Sie sicher bemerkt haben, wird der eben erstellte Prozess bei jeder Änderung in der Seite ausgeführt. Für das Speichern des Erstellzeitpunktes und des Erstellers eines neuen Datensatzes wurden der Seite und der Tabelle die beiden Items P3_CRETED_BY und P3_CREATED_ON zugefügt.

Um die Werte dieser beiden Items über einen weiteren Prozess zu setzten, können Sie wie im vorherigen Beispiel einen Prozess erstellen, der via. PL/SQL die Items setzte. Allerdings soll dieser Prozess nur einmalig ausgeführt werden, wenn ein neuer Datensatz angelegt wird. Für solche Fälle nutzen Sie in APEX sogenannte conditions.

Über eine condition können Sie beispielsweise prüfen, ob ein bestimmter Button gedrückt wurde. Sie können über diese Bedingungen feststellen, ob ein Item null ist oder einen definierten Wert besitzt. Für komplexere Prüfungen nutzen Sie einfach SQL oder PL/SQL.

Abbildung 39: Condition
Abbildung 39: Condition

Eine condition definieren Sie über die Eigenschaften eines Prozesses. Dort finden Sie unter conditions – in der deutschsprachigen Version “Serverseitige Bedingung” genannt – verschiedene Möglichkeiten. Für dieses Beispiel genügt es, den Prozess nur dann ausführen zu lassen, wenn der Button CREATE betätgt wird. 

Oracle APEX 18.2 Tutorial Teil 3 – Application Builder

Anwendungen erstellen Sie in APEX mit dem Application Builder. Hierzu mehr im 3. Teil des deutschsprachigem APEX Einsteiger Tutorials.

Application Builder

Der Application Builder ist das Tool in APEX, mit dem Sie Anwendungen erstellen, bearbeiten und verwalten.

Abbildung 14: Application Builder
Abbildung 14: Application Builder

Öffnen Sie den App Builder über die gleich benannte Schaltfläche oder über den zugehörigen Menüpunkt, so erhalten Sie eine tabellarische Übersicht aller Anwendungen Ihres Workspaces. Diese werden in einem Interactive Report dargestellt. Dieses bietet Ihnen mannigfaltige Möglichkeiten, die Anwendungen zu filtern, gruppieren, sortieren, usw.

Abbildung 15: Anwendungsübersicht im Application Builder
Abbildung 15: Anwendungsübersicht im Application Builder

Anwendung erstellen

Neue Anwendundungen legen Sie über einen Wizzard an, den Sie über den Create Button – siehe Abbildung 15 – starten.

Im ersten Schritt werden Sie gefragt, welche Art von Anwendung Sie erstellen wollen. Wählen Sie New Application.

Abbildung 16: Create an Application
Abbildung 16: Create an Application

In der nächsten Seite (Abbildung 16) können Sie neben dem Namen der Anwendung auch diverse Eigenschaften festlegen.

Über den Button in der Select-List Appearance können Sie in einem Details-Dialog das Aussehen Ihrer Anwendung festlegen.

Abbildung 17: Appearance
Abbildung 17: Appearance

Wählen Sie neben einem Themen, ein Icon für diese Anwendung und im Navigation-Bereich Side Menü. Speichern Sie die Eingaben.

Sie gelangen wieder zurück zur Hauptseite des Application Wizzards. Wie Sie in Abbildung 16 sehen, wurde bereits eine Home Seite der Anwendung hinzugefügt. Auf das Anlegen weiterer Seiten verzichten ich an dieser Stelle. Auch zusätzliche Features sind für dieses Tutorial nicht von Belang.

Klicken Sie auf Create Application.

Der Wizzard legt nun die Web-Anwendung an und öffnet diese im Application Builder.

Abbildung 18: Cooking with APEX
Abbildung 18: Cooking with APEX

Anwendung ausführen

Starten Sie die Applikation Cooking with APEX durch anklicken des Buttons Run Application.

Es öffnet sich nun in einem neuen Registers Ihres Browsers die App.

Abbildung 19: Sign In
Abbildung 19: Sign In

Anmelden können Sie sich mit Ihrem APEX-Account. Auf Ihrem Rechner wird dabei ein Cookie mit der SessionId erzeugt.

Die Anwendung startet mit der Home -Seite.

Abbildung 20: Cooking with APEX
Abbildung 20: Cooking with APEX

Im Kopfbereich finden Sie links neben dem Anwendungstitels einen Burger-Button über den Sie das Seiten Menü verkleinern können.

Diese Minimaldarstellung des Menüs erhalten Sie auch wenn Sie die Anwendung z.B. auf einem Smartphone öffnen. APEX-Anwendungen, die auf dem aktuellen Universal Theme beruhen, sind von Haus aus responsive. Sie passen Ihr Aussehen also der Device -Größe an.

Da Sie die Anwendung aus der APEX-Entwicklungsumgebung heraus aufgerufen haben, wird Ihnen im Fußbereich der Anwendung die sogenannte Developer Toolbar angezeigt. Diese bietet verschiedene Werkzeuge, die Sie bei der Anwendungsentwicklung unterstützen werden.

Klicken Sie dort bitte einmal auf den Button Edit Page 1. Es öffnet Sie in dem ursprünglichen Register Ihres Browsers indem Sie die APEX-Anwendung geöffnet hatten, der Page Designer.

Abbildung 21: APEX Page Designer
Abildung 21: APEX Page Designer

Dieser zeigt nun alle Bestandteile der eben geöffneten Seite 1 (Home). Hier definieren Sie den Inhalt der Seiten und legen das Verhalten Ihrer Anwendung fest.

Die Seite selbst, können Sie direkt über das Run-Icon der Toolbar – siehe Abbildung 21 – laden. Auf diese Weise können Sie sehr intuitiv zwischen Ihrer Anwendung und der Entwicklungsumgebung hin und her wechseln.

Beitragsreihe

Im Teil 4 wird es um das Erstellen  von Seiten mit dem Page Designer gehen.

 

 

Oracle APEX 18.2 Tutorial Teil 2 – SQL Commands

Im zweiten Teil des Einführungstutorials geht es um die Ausführung von DDL Anweisungen über das Modul SQL Commander. 

In diesem Teil des Tutorials soll der Rest des Datenmodells angelegt werden. Hierzu kommt zu Anschauungszwecken das Werkzeug SQL Commands zum Einsatz.

SQL Commands

Im ersten Teil der Beitragsreihe ging es darum eine Tabelle mit dem Objects Browser zu erzeugen. APEX bietet unter dem SQL-Workshop neben diesem Tool mit dem SQL Commands ein weiteres Werkzeug mit dem Sie direkt mit der Datenbank interagieren können.

Abbildung 9: SQL Commands
Abbildung 9: SQL Workshop – SQL Commands

Über das Tool SQL Commands ist es möglich einfach Freihand SQL Befehle und SQL-Scripte auszuführen.

Abbildung 10: SQL Commands
Abbildung 10: SQL Commands

Im oberen Textfeld können Sie SQL bzw. PL/SQL Befehle eingeben. Über den Button Run führen Sie diese aus.

Mit Find Tables können Sie sich die Tabellen Ihres Workspaces anzeigen lassen. Klicken Sie in der Übersicht von “Find Tables” auf den Tabellennamen wird Ihnen die Struktur der Tabelle präsentiert.

Abbildung 11: Find Tables
Abbildung 11: Find Tables

Die Skripte, die Sie mit SQL Commands erstellt haben, lassen sich in APEX über die Schaltfläche Save speichern. Laden können Sie diese dann über den Tab Saved SQL.

Tabellen und Trigger anlegen

Das folgende SQL-Skript (Listing 1) erzeugt für die noch fehlenden Tabellen ingredients, processing_steps und product_classification. Zu jeder Tabelle wird je ein Trigger angelegt, über den ein künstlicher Schlüssel als Primärschlüssel erzeugt wird.

Das Skript habe ich mit einem weiteren APEX-Tool namens Quick SQL erzeugt. Anschließend habe ich noch einige kleine manuellen Anpassungen vorgenommen. Wenn Sie sich näher mit der schnellen Erzeugung von Datenmodellen beschäftigen möchten, werden Sie doch einfach mal einen (lohnenswerten) Blick auf den Beitrag Oracle Apex – Quick SQL.

-- create tables
create table co_processing_step (
  id number not null constraint co_processing_step_id_pk primary key,
  name varchar2(255),
  description varchar2(4000),
  media_name varchar2(255),
  media_object blob,
  media_filename varchar2(255),
  media_mime_type varchar2(48),
  media_size number,
  media_content_type varchar2(4000),
  order_number number,
  created date not null,
  created_by varchar2(255) not null,
  updated date not null,
  updated_by varchar2(255) not null
)
;

create table co_ingredients (
  id number not null constraint co_ingredients_id_pk primary key,
  processing_step_id number
  constraint co_ingredient_processing_st_fk
  references co_processing_step on delete cascade,
  name varchar2(255),
  quantity number,
  quantity_unit varchar2(10),
  calories number,
  product_classification_id number,
  created date not null,
  created_by varchar2(255) not null,
  updated date not null,
  updated_by varchar2(255) not null
)
;

create table co_product_classification (
  id number not null constraint co_product_classif_id_pk primary key,
  name varchar2(255),
  description varchar2(4000),
  created date not null,
  created_by varchar2(255) not null,
  updated date not null,
  updated_by varchar2(255) not null
)
;

CREATE SEQUENCE co_processing_step_seq ORDER;

CREATE SEQUENCE co_ingredients_seq ORDER;

CREATE SEQUENCE product_classification_seq ORDER;

-- triggers
create or replace trigger co_processing_step_biu
   before insert or update
   on co_processing_step
   for each row
 begin
   if :new.id is null then
      select co_processing_step_seq.nextval into :new.id from dual;
   end if;
   if inserting then
     :new.created := sysdate;
     :new.created_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
   end if;
   :new.updated := sysdate;
   :new.updated_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
end co_processing_step_biu;
/

create or replace trigger co_ingredients_biu
   before insert or update
   on co_ingredients
   for each row
  begin
      if :new.id is null then
         select co_ingredients_seq.nextval into :new.id from dual;
      end if;
      if inserting then
        :new.created := sysdate;
        :new.created_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
      end if;
      :new.updated := sysdate;
      :new.updated_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
end co_ingredients_biu;
/

create or replace trigger co_product_classification_biu
   before insert or update
   on co_product_classification
   for each row
  begin
    if :new.id is null then
        select product_classification_seq.nextval into :new.id from dual;
    end if;
    if inserting then
      :new.created := sysdate;
      :new.created_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
    end if;
    :new.updated := sysdate;
    :new.updated_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
end co_product_classification_biu;
/

-- indexes
create index co_ingredients_i1 on co_ingredients (processing_step_id);
create index co_product_classification_i1 on co_product_classification (ingredient_id);

Listing 1: Create ingredients , processing_steps, …

Kopieren Sie den Inhalt des Skriptes in den Eingabebereich des SQL Commands und klicken Sie dann auf Run.

Abbildung 12: Skript ausführen
Abbildung 12: Skript ausführen

Sie bekommen anschließend eine Zusammenfassung und mögliche Fehlermeldungen angezeigt.

Select-Statements ausführen

Über das Tool SQL Commands lassen sich aber auch Select-Anweisungen entwickeln und ausführen.

Um zu prüfen, welche Tabellen in Ihrem Schema vorhanden sind, können Sie beispielsweise dieses Select-Staement bemühen.


select *
  from user_tables
 where table_name like 'CO%'

Führen Sie es einfach in SQL Commands aus. Das Ergebnis wird Ihnen als Tabelle angezeigt.

Abbildung 13: Select Statement in SQL Commands
Abbildung 13: Select Statement in SQL Commands

Beitragsreihe

Konfigurationsmanagement für Oracle APEX -Teil 1 Versionsverwaltung

Versionsverwaltung und Konfigurationsmanagement ist für den Entwicklungszyklus gerade auch im Rahmen agiler bzw. iterativer Entwicklung von Oracle Application Express (APEX) Anwendungen ein wichtiges aber sicherlich oft vernachlässigtes Thema. Im ersten Teil geht es um die Versionierung und das Betreiben unterschiedlicher paralleler Versionen in einer Entwicklungsumgebung.

Was versteht man unter Konfigurationsmanagement?

Laut Wikipedia versteht man unter Konfigurationsmanagement 

Konfigurationsmanagement (KM; englisch configuration managementCM) ist eine Managementdisziplin, die organisatorische und verhaltensmäßige Regeln auf den Lebenslauf eines Produkts und seiner Konfigurationseinheiten [..] anwendet.

[..]

Es sind vier Teilgebiete (Teilprozesse) des KM zu unterscheiden:

  • Konfigurationsidentifizierung (KI),
  • Konfigurationsbuchführung (KB),
  • Konfigurationsüberwachung (KÜ) und
  • Konfigurationsaudit (KA).

Ihre koordinierte Umsetzung ist für ein erfolgreiches Konfigurationsmanagement unumgänglich.

Quelle: https://de.m.wikipedia.org/wiki/Konfigurationsmanagement, 24.06.2018

Im Rahmen der Entwicklung von Software spricht man von Software-Configuration-Management(SCM).

Wie bei vielen Managementsystemen kommen solche Definitionen meist sehr sperrig daher. Im Rahmen der Anwendungsentwicklung mit APEX soll hier eine pragmatische Umsetzung des Ganzen gezeigt werden.

Agile Softwareentwicklung mit APEX

Beginnt man ein neues APEX-Projekt, so wird man je nach Projektmanagementmethode und Größe des Projektes unterschiedlich an die Sache heran gehen.

Aus meiner Sicht bietet sich aber gerade ein agiles Vorgehen für die Entwicklung von APEX-Anwendungen an. Welches konkrete Vorgehensmodell Sie dabei wählen ist zweitrangig.

Bei einem agilen Ansatz starten Sie mit einem ersten Teil Ihres Datenmodells und entwickeln dann sehr zügig die jeweiligen Seiten in Ihrer Anwendung. So erhalten Sie sofort vorzeigbare Anwendungsteile, die Sie mit Ihren Kunden, Productowner, … durchsprechen und abstimmen können.

Aber auch wenn Sie grossschrittiger vorgehen müssen und Ihren Kunden fertig entwickelte Releases ausliefern, gehen Sie als Entwickler wahrscheinlich itterativ an die Sache heran.

Sie starten also mit einem leeren Schema und einem Workspace, erstellen die ersten Tabellen. Dann kann schon die erste Version der Anwendung per Application Wizzard erzeugt werden. Durch die Nutzung der Metadaten, welche die Datenbank APEX zur Verfügung stellt, fügen Sie die ersten Seiten der Anwendung hinzu. Jetzt erweitern Sie das Datenmodell und ergänzen die zugehörigen Seiten in der Anwendung. Die Businesslogik lagern Sie in PL/SQL-Packages aus. Je nach Komplexität und Umfang des Projektes abstrahieren Sie zwischen Datenmodell und Anwendung noch über Views. Auf diese Weise entstehen sukzessive weitere Datenbankobjekte und Anwendungsteile.

Alle diese Teile sind Bestandteil einer sogenannten Konfigurationseinheit.

Sie erzeugen also eine Kette von Builds, die dann weiter zu einem Release der Anwendung führen.

Anschließend oder auch schon parallel werden Sie die Anwendung in einem Testsystem mehr oder weniger ausgiebig testen (lassen) und dann an Ihren Kunden ausliefern.

Je nach Anwendung entwickeln Sie das System weiter. In Ihrem Entwicklungssystem steht dann mit großer Wahrscheinlichkeit nicht mehr die Version, mit der Sie Ihre Kunden beliefert haben, zur Verfügung. Dies führt bei Supportanfragen Ihres Kunden dazu, dass Sie in einem Supportsystem, das ausgelieferte System mit entsprechendem Releasestand wiederherstellen müssen. Fixen Sie den Fehler in dem (veralteten Release), so muss die so bereinigte Version bei Ihren Kunden eingespielt werden. Zusätzlich müssen Sie nun das Problem auch noch in Ihrem aktuellen Entwicklungsstand der Anwendung beheben.

Noch komplizierter wird dieses Szenario, wenn Sie verschiedene Releasestände an mehrere Kunden ausgeliefert haben, dabei aber evtl. auch noch individuelle Anforderungen einzelner Kunden berücksichtigten mussten.

Aus diesem Lebenszyklus einer Software ergeben sich verschiedene Probleme:

  • Wie Verwalten Sie unterschiedliche Versionen (Releasestände und Builds) Ihrer APEX-Anwendungen ?
  • Wie erstellen Sie Test-, Integrations- und Supportsysteme, in denen Sie verschiedene Versionen Ihrer Anwendung verwalten können ?
  • Wie führen Sie parallele Entwicklungslinieren (Forkes) wieder in Ihrer Basisanwendung zusammen ?
  • Wie dokumentieren und identifizieren Sie die Änderungen an Ihren Anwendungsversionen inkl. aller Bestandteile (Tabellen, Views, Triggers, PL/SQL-Packages,…) ?

Konfigurationseinheiten: Komponenten einer APEX Anwendung

Aus welchen Bestandteilen besteht denn nun eine APEX-Anwendung?

Wenn Sie versuchen, diese Frage zu beantworten, schauen Sie sich doch einfach an, was Sie alles benötigen, um eine APEX-Anwendung in einem Produktivstem zum Laufen zu bringen.

Vorausgesetzt, dass eine laufende Oracle-Datenbank mit eingerichtetem APEX (in der korrekten Version) zur Verfügung steht, benötigen Sie zuerst ein Export-File der APEX-Anwendung. Dabei handelt es sich um ein SQL-Skript, mit dem Sie eine Anwendung in APEX exportieren und importieren können.

Die Anwendung basiert natürlich auf Tabellen und verwendet verschiedene anderen Datenbankobjekte. APEX bietet die Möglichkeit, diese Datenbankobjekte über entsprechende DDL-Skripte bei der Installation bzw. dem Import einer Anwendung anlegen zu lassen.

Um diese Tabellen initial mit Stammdaten zu füllen, erstellen Sie entsprechende DML-Skripte.

All diese Skripte können Sie in den sogenannten Supporting Objects hinterlegen, die Sie im Application Builder über die gleichnamigen Schaltfläche aufrufen können.

Abbildung 1: Supporting Objects

Auch für das Upgraden einer produktiven Anwendung, ist es möglich in den Supporting Objects Skripte zu hinterlegen, welche die Änderung zum Vorgänger-Release abdecken.

Natürlich kann man das benötigte Datenmodell über einen Dump oder separate SQL-Skripte in einem Produktivstem einrichten. Da die Supporting Objects aber Bestandteil einer APEX-Anwendung sind und diese mit in dem Anwendungs-Exportfile vorhanden sind, ist es am geschicktesten, diese Möglichkeit zu verwenden. Gerade auch im Hinblick auf die Versionsverwaltung bringt das einige Vorteile mit sich.

Versionierung

Damit Sie den Überblick über die Versionsstände Ihrer Anwendung behalten, ist es ganz hilfreich für etwas Ordnung zu sorgen. APEX bietet Ihnen die Möglichkeit, Ihren Anwendungen beschreibende Eigenschaften mitzugeben. Diese Eigenschaften finden Sie etwas versteckt hinter dem Button Edit Application Properties im Application Builder.

Abbildung 2: Anwendungseigenschaften

Wie Sie hier sehen können, finden Sie ein Textfeld namens Release. Hier können Sie für jede Version Ihrer Anwendung den zugehörigen Releasestand festhalten. Das klingt erst einmal banal, bringt aber den praktischen Vorteil mit sich, dass Sie bei einer archivierten oder  ausgeliederten Anwendung wissen, welche Version Ihrer Anwendung Sie gerade vor sich haben.

Aber wie können Sie eine Versionsnummer konzipieren, die Ihnen einen Überblick verschafft?

Es gibt da sicher verschiedene Möglichkeiten. Ich möchte Ihnen hier die von mir bevorzugte Variante vorstellen.

<Hauptentwicklungslinie>.<Entwicklungszweig>.<Build>

Zu Begin einer Anwendungsentwicklung, also vor dem ersten Release geht es also mit 0.0.1 los.

Nach einer Iteration oder dem erreichen eines Meilensteins erhöhen Sie die Build Nr. auf 0.0.2.

Haben Sie eine releasefähige Version Ihrer Anwendung erreicht, so erhöhen Sie die erste Stelle um 1.

Version 1.0.0

Diese Version nehmen Sie produktiv und entwickeln an der Anwendung weiter.

Es entsteht also Build 1.0.1… 1.0.2 …

Müssen Sie einen Fehler in Version 1.0.0 beheben, erstellen Sie einen Forke Ihrer Anwendung und geben diesem die Version 1.1.0. Änderungen an dieser Version laufen jetzt paralell zu Ihrer Hauptentwicklungslinie (Baseline), die bereits bei Version 1.0.2 angelangt ist.

Für einen anderen Kunden erweitern Sie die Version 1.0.0 um eine individuelle Funktion. Diese ist dann wieder ein Forke der Version 1.0.0 und wird mit 1.2.0 benannt.

Um nun den Aufwand dauerhaft in den Griff zu bekommen und den Überblick nicht zu verlieren, ist es ratsam, die Bugfixes aus den Strang 1.1.x und evtl. die kundenspezifischen Funktionen in den Basisentwicklungsstrang zu integrieren (merge). Was in APEX nicht immer so einfach ist. Aber dazu im zweiten Teil der Beitragsreihe später mehr.

Die neuen Funktionen und Bugfixes fulminieren dann irgendwann in einem neuen Release 2.0.0.

Ihre Entwicklungslinien sehen jetzt so aus.

Abbildung 3: Entwicklungsstränge

Im Application Builder werden Ihnen Ihre Anwendungen über ein Interaktiven Report präsentiert. Blenden Sie dort über Actions >> Columns die Version und evtl. andere für Sie relevante Spalten ein. Anschließend speichern Sie sich über Actions >> Report >> Save Report Ihre individuelle Berichtsansichten.

Abbildung 4: Version anzeigen

Hilfreich finde ich auch die Zuordnung von Anwendungen zu Gruppen. Z.B. können Sie so zwischen Test, Produktiv, Support und Entwicklung unterscheiden.

Anwendungsgruppen (Application Groups) legen Sie über das Menü App Builder >> Workspace Utilities-Database >> Application Groups fest. Über die Anwendungseigenschaften (Button Edit Application Properties) ordnen Sie Ihre Anwendung einer Gruppe zu. Diese Gruppen lassen sich genau wie die Version im Interaktiven Report einblenden.

Abbildung 5: Anwendungen gruppieren

Die Abbildung 5 zeigt ihnen eine aufbereitete Übersicht der Anwendungen in APEX, die bei dem oben skizzierten Projektablauf entstanden sind.

Archivierung von Releaseständen

APEX bietet verschiedene Möglichkeiten Anwendungen zu archivieren. Die Basis für diese Ansätze ist ein Exportfile. Über den Application Builder können Sie alle Eigenschaften eines APEX-Programms in Form eines SQL-Skriptes exportieren.

Abbildung 6: Anwendung exportieren

Diese Datei beinhaltet die komplette Definition aller Seiten, Regionen, Items, Prozesse,… aber auch aller Shared Components wie Dateien oder Listen und – wie bereits erwähnt – die Supporting Objects. Nutzen Sie all diese Möglichkeiten, so können Sie die komplette Anwendung inkl. der Datenbankobjekte in einer einzigen Datei zusammenfassen und später diesen (Release)Stand wiederherstellen.

Somit ergibt sich als einfache Möglichkeit verschiedene Versionen einer APEX-Anwendung zu archivieren, der regelmäßige manuelle Export der Anwendung und Speicherung in einer nach Versionen benannten Ordnern. Diese Idee kann man nun mit einer Software zur Versionsverwaltung wie z.B. SVN oder GIT kombinieren. Im Internet finden Sie verschiedene Beispiele, wie Sie mit verschiedenen Tolles diesen Weg automatisieren können.  In dem Artikel ORACLE APEX DEPLOYMENT:YOU’RE DOING IT WRONG stellt Martin D’Souza die Möglichkeit vor, via. SQLcl zu automatisieren.Mit APEX selbst wird der APEX Java Exporter ausgeliefert, über den Ihnen viele Möglichkeiten zum Exportieren von Anwendungen und Komponenten zur Verfügung stehen.

Wählen Sie beim manuellen Exportieren einer Anwendung das File Format “Database”, so archiviert APEX die Datei im sogenannten Export Repository. Dieses finden Sie in den Workspace Utilities-Database >> Export >> Export Repository (in der rechten Sidebar). Allerdings bietet die Tabelle nicht wirklich viele Informationen zu den archivierten Anwendungen.

Abbildung 7: Export Repository

Einen anderen Ansatz finden Sie in diesem Blog-Beitrag. Dabei werden die einzelnen Bestandteile (Seiten) einer Anwendung separat extrahiert und übersichtlich in einem Verzeichnisbaum bzw. der Datenbank gespeichert. Dieser Ansatz lässt auch automatisieren.

Eine weitere ebenfalls automatisierbare Variante bietet Oracle mit einem seiner Packaged Applications “APEX Application Archive”. Mit dieser Applikation lassen sich Backups Ihrer Anwendungen erstellen, die dann in der Datenbank verwaltet werden. Erweitern Sie das Ganze noch mit Hilfe des Datenbankschedulers ( dbms_schedule ), können Sie die Archivierung komplett automatisieren. Wie Sie das im Einzelnen funktioniert, können Sie sich in dieser Beitragsreihe genau ansehen.

Abbildung 8: Anwendungsarchiv auswählen

Ich persönlich favorisiere diese Möglichkeit, da Sie sich sehr schnell einrichten und anpassen lässt und mit der Package Application “APEX Application Archive” ein Programm zur Verwaltung der einzelnen Releasestände zur Verfügung steht.

Parallele Versionen in APEX betreiben

Aber wie können Sie diese unterschiedlichen Versionen Ihrer Anwendungen in APEX parallel bearbeiten und betreiben?

Eine einfache Möglichkeit ist es eine Kopie Ihrer Anwendung in dem Workspace anzulegen.

Allerdings gehören zu Ihrer Anwendung auch ein Datenmodell und die entsprechenden Datenbankobjekte. Diese werden zwischen den einzelnen Releaseständen Ihrer Anwendungen voneinander abweichen.

APEX bietet für dieses Problem eine einfache aber sehr elegante Lösung. Sie können Ihrem Workspace weitere Datenbank-Schemas zuweisen.

Dazu gehen Sie  so vor.

1. Melden Sie sich am Administration Service als Admin an.

2. Im Menü finden Sie unter Manage Workspace den Eintrag Manage Schema Assignment.

Abbildung 9: Manage Schema Assignment

3. Über Add Schema können Sie nun über einen Wizzard weitere Schemas anlegen.

Hinweis: APEX bietet hier zwei Möglichkeiten. Sie können ein vorhandenes Schema hinzufügen oder ein Neues anlegen. Diese Zweite Version lief in der von mir verwendeten Version auf einen Fehler. Um das Problem zu umgehen, legen Sie also einen User von Hand (z.B. über sqlplus) an und ordnen Sie diesen hier zu.

4. Melden Sie sich wieder als Entwickler an Ihrem Workspace an.

5. Exportieren Sie die Anwendung, die Sie in einem neuen Schema als neues Build/Release anlegen möchten.

6. Importieren Sie die Anwendung und wählen Sie im Import-Wizzard das neue Schema aus.

Abbildung 10: Schema zuordnen

7. Klicken Sie auf Next >.

8. In diesem Schritt wählen Sie die Option “Install Supporting Objects” = YES.

Abbildung 11: Install Supported Objects

9. Setzen Sie die Installation fort und folgen Sie dem Wizzard. Dabei werden nun die DDL-Skripte, welche Sie in den Supporting Objects hinterlegt haben, ausgeführt und somit die benötigten Datenbankobjekte in dem neuen Schema erzeugt.

Beitragsreihe

Oracle-Datenbank: Teamwork mit Bordmitteln

Synchronisieren Sie mit DDL-Triggern gemeinsames Arbeiten durch Ein -und Ausschenken von Objekten. Und das sogar für beliebige Datenbanktools.

Viele namhafte Tools zur Datenbankentwicklung bieten Funktionen zur Unterstützung von Entwicklerteams. So können Sie Datenbankobjekte zur Bearbeitung ausschenken. Andere Entwickler, können solange keine Änderungen an diesem Objekt mehr vornehmen, bis dieses wieder eingecheckt wurde. Leider funktioniert das meist nicht werkzeugübergreifend, da diese Tools oft eigene Tabellen für die Verwaltung der Bearbeitungszustände der Datenbankobjekte nutzen.

In diesen Beitrag möchte ich Ihnen eine einfache, aber effektive Möglichkeit zeigen, wie Sie so eine Funktion mit Bordmitteln der Oracle Datenbank selber entwickeln können. Vor allem ist bei der vorgestellten Lösung beachtenswert, dass Sie werkzeugübergreifend funktioniert.

Datenmodell

Benötigt werden zwei Tabellen. Über die Tabelle ddl_object_state wird der Bearbeitungsstand der Datenbankobjekte verwaltet. Sobald ein Entwickler ein Objekt bearbeiten möchten, blockt er dieses. Dabei wird ein Datensatz in dieser Tabelle eingefügt. Ist die Bearbeitung abgeschlossen, so wird das Objekt wieder freigegeben und der zugehörige Einträge entfernt.


CREATE TABLE ddl_object_state (
          object_type VARCHAR2(50 BYTE),
          object_owner VARCHAR2(32 BYTE),
          object_name VARCHAR2(32 BYTE), 
          changed_on DATE,
          changed_by VARCHAR2(32 BYTE)
)
/

Listing 1: Tabelle ddl_object_state

In der zweiten Tabelle ddl_object_state_hist wird beim Ein- und Auschecken neben den Statusinformationen auch ein DDL-Extrakt des bearbeiteten Objektes eingetragen. So dass Sie auf einen Stand zu Beginn und zum Ende der Bearbeitung zurückgreifen können.


CREATE TABLE ddl_object_state_hist
(
   object_type VARCHAR2 (50 BYTE),
   object_owner VARCHAR2 (32 BYTE),
   object_name VARCHAR2 (32 BYTE),
   status_code VARCHAR2 (20 BYTE),
   changed_on DATE,
   changed_by VARCHAR2 (32 BYTE),
   ddl_sql CLOB
)
/

Listing 2: Tabelle ddl_object_state_hist

PL/SQL Package ddl_util

Das PL/SQL Package fasst die benötigten Prozeduren zusammen.

PROCEDURE lock_object (
     p_object_type VARCHAR2,
     p_owner VARCHAR2,
     p_name VARCHAR2,
     p_trace_hist BOOLEAN DEFAULT true
  ) IS
    l_c NUMBER;
    l_locked_by VARCHAR2(32);
    l_changed_on DATE;
    l_ddl_extract CLOB;
BEGIN
    SELECT
      COUNT(*)
     INTO l_c
    FROM ddl_object_state
    WHERE object_owner = p_owner
       AND object_name = p_name;

  IF l_c = 0 THEN
       INSERT INTO ddl_object_state (
          object_type,
          object_owner,
          object_name,
          changed_on,
          changed_by )
       SELECT
          p_object_type,
          p_owner object_owner,
          p_name object_name,
          SYSDATE changed_on,
          ora_login_user changed_by
       FROM dual;

    IF p_trace_hist THEN
       l_ddl_extract := dbms_metadata.get_ddl(p_object_type,p_name);

       INSERT INTO ddl_object_state_hist (
          object_type,
          object_owner,
          object_name,
          ddl_sql,
          status_code,
          changed_on,
          changed_by )
       SELECT
          p_object_type,
          p_owner object_owner,
          p_name object_name,
          l_ddl_extract,
          'LOCK',
          SYSDATE changed_on,
          ora_login_user changed_by
       FROM dual;
    END IF;
 ELSE
      SELECT
          changed_by,
          changed_on
      INTO
        l_locked_by,
        l_changed_on
      FROM ddl_object_state
      WHERE object_owner = p_owner
        AND object_name = p_name;

     IF l_locked_by &lt;&gt; ora_login_user THEN
        raise_application_error(-20001,'Object was already locked by '
             || l_locked_by
             || ' at '
             || TO_CHAR(l_changed_on,'dd.mm.yyyy HH24:Mi:ss') );
     END IF;
 END IF;

END;

Listing 3: Procedure lock_object

Die Prozedur lock_object erhält als Parameter beim Aufruf den Objekttyp, den Besitzer des Objektes und den Namen des Objektes.

Als erstes prüft die Prozedur, ob das Objekt bereits von einem anderen User ausgescheckt wurde. Ist das der Fall wird ein application_error -20001 ausgelöst. Wenn das Objekt nicht in Bearbeitung ist, trägt die Prozedur diese in der Tabelle ddl_object_state ein und extrahiert eine DDL-Anweisung des Objektes. Diese wird dann in der Tabelle ddl_object_state archiviert.

PROCEDURE unlock_object (
     p_object_type VARCHAR2,
     p_owner VARCHAR2,
     p_name VARCHAR2,
     p_trace_hist BOOLEAN DEFAULT true,
     p_secure_mode BOOLEAN DEFAULT true) 
IS
     l_c NUMBER;
     l_locked_by VARCHAR2(32);
     l_changed_on DATE;
     l_ddl_extract CLOB;
BEGIN
     SELECT COUNT(*)
       INTO l_c
     FROM ddl_object_state
     WHERE object_owner = p_owner
       AND object_name = p_name;

 IF l_c = 1 THEN
     SELECT
       changed_by,
       changed_on
     INTO
       l_locked_by,
       l_changed_on
     FROM ddl_object_state
     WHERE object_owner = p_owner
       AND object_name = p_name;

    IF p_secure_mode AND l_locked_by &lt;&gt; ora_login_user  THEN
        raise_application_error(-20002,'You have no permissions in secure mode to unlock the object. The Object was locked by '
              || l_locked_by
              || ' at '
              || TO_CHAR(l_changed_on,'dd.mm.yyyy HH24:Mi:ss') );
    END IF;

    DELETE ddl_object_state
      WHERE object_owner = p_owner
        AND object_name = p_name;

    IF p_trace_hist THEN
      l_ddl_extract := dbms_metadata.get_ddl(p_object_type,p_name);

      INSERT INTO ddl_object_state_hist (
            object_type,
            object_owner,
            object_name,
            ddl_sql,
            status_code,
            changed_on,
            changed_by )
      SELECT
            p_object_type,
            p_owner object_owner,
            p_name object_name,
            l_ddl_extract,
            'UNLOCK',
            SYSDATE changed_on,
            ora_login_user changed_by
      FROM dual;
   END IF;
 
 END IF;

END;

Listing 4: Procedure unlock_object

Um ein Datenbankobjekt nach der Bearbeitung wieder frei zu geben, nutzen Sie die Prozedur unlock_object. Diese prüft im Secure Mode, ob Sie im Vorfeld das entsprechende Objekt auch selber ausgelockt haben. Ist das nicht der Fall, wird der Applikationsfehler -20002 ausgelöst. Ein von Ihnen ausgelocktes Objekt wird durch das Löschen des zugehörigen Eintrags in der Tabelle ddl_object_state wieder zur Bearbeitung frei gegeben. In der Tabelle ddl_object_state_hist wird wieder ein DDL-Extrakt gespeichert.

Objekte, die von einem anderen Entwickler als Sie selbst geblockt wurden, können Sie durch das Setzen des Parameters p_secure_mode = false wieder freigegeben. Auch dabei wird ein Historiendatensatz erzeugt. Anschließend können Sie so den Besitz durch Aufrufen von lock_object übernehmen.

PROCEDURE check_object_state (
             p_object_type VARCHAR2,
             p_owner VARCHAR2,
             p_name VARCHAR2
           );

Listing 5: Procedure check_object_state

Mit der dritten Procedur check_object_state können Sie einfach den Bearbeitungsstand eine Datenbankobjektes überprüfen.

Wie bereits erwähnt, speichern die Prozeduren beim Ein- und Auschecken jeweils ein DDL-Statements in der Historientabelle. Wenn Sie eine genaue Protokollierung aller Anpassungen benötigen, möchte ich Sie auf den Artikel Oracle Datenbankentwicklung: DDL-Trigger zur Auditierung von Strukturänderungen hinweisen.

DDL-Trigger

Anschließend benötigen Sie noch zwei DDL-Trigger, die Sie in dem Entwicklungsschema anlegen müssen.


CREATE OR REPLACE TRIGGER trg_ddl_util_before 
     BEFORE ALTER OR DROP 
    ON DATABASE 
DECLARE 
BEGIN
      IF ora_dict_obj_name NOT IN (
               'TRG_DDL_UTIL_BEFORE',
               'TRG_DDL_UTIL_AFTER',
               'DDL_UTIL' ) 
          AND ora_login_user NOT IN (
               'SYS',
               'SYSTEM' ) 
      THEN
         ddl_util.lock_object(ora_dict_obj_type,ora_dict_obj_owner,ora_dict_obj_name);
      END IF;
END;
/

Listing 6: Trigger trg_ddl_util_before

Der Trigger trg_ddl_util_before wird bei ALTER- und DROP-Anweisungen ausgeführt. Für einen Create-Befehl macht dieser Zeitpunkt keinen Sinn, da das Objekt in der Datenbank noch nicht vorliegen kann.

Bei der Verwendung von DDL-Triggern sollten Sie besondere Vorsicht walten lassen. Da diese Trigger auch bei Änderungen an sich selbst gefeuert werden, kann es vorkommen, dass man bei einer fehlerhaften Programmierung evtl. keine Änderungen mehr an den Triggern vornehmen kann. Aus diesem Grund prüft der Trigger zu Beginn, ob Änderungen an ihm selbst bzw. an dem Trigger trg_ddl_util_after ausgeführt werden sollen. Des Weiteren werden die Accounts das und system von der Verarbeitung ausgenommen. Allerdings würde auch ein invaliden Package ddl_util zu Problemen führen können.

Die eigentlich Logik des Triggers besteht dann nur noch im Aufruf der Prozedur ddl_util.lock_object. Diese prüft, ob das Objekt von einem anderen Entwickler zur Bearbeitung ausgescheckt wurde. In diesem Fall wird wie bereits erwähnt eine Exception gefeuert und so die Änderungen an dem Objekt dem aktuellen Nutzer verwährt. Bei Objekten, die noch nicht ausgescheckt sind, wird das über diesen Trigger automatisch erledigt.

Alternativ kann es aber auch durchaus sinnvoll sein, dass Ausschecken eines Objektes vor der Bearbeitung manuell durch das Aufrufen von  ddl_util.lock_object durchzuführen. So erspart man sich, dass Änderungen beim Speichern durch den Trigger erst im Nachhinein verworfen werden.

CREATE OR REPLACE TRIGGER trg_ddl_util_after 
    AFTER CREATE OR DROP 
 ON DATABASE 
DECLARE 
BEGIN
    IF ora_dict_obj_name NOT IN (
              'TRG_DDL_UTIL_BEFORE',
              'TRG_DDL_UTIL_AFTER',
              'DDL_UTIL')
    AND ora_login_user NOT IN (
              'SYS',
              'SYSTEM' )
    THEN
        IF ora_sysevent = 'CREATE' THEN
            ddl_util.lock_object(ora_dict_obj_type,ora_dict_obj_owner,ora_dict_obj_name);
       END IF;

       IF ora_sysevent = 'DROP' THEN
           ddl_util.unlock_object(ora_dict_obj_type,ora_dict_obj_owner,ora_dict_obj_name,false,true);
       END IF;

    END IF;
END;
/

Listing 7: Trigger trg_ddl_util_after

Wird nun ein neues Objekt angelegt, kann dieses nicht im Vorfeld geblockt werden. In so einem Fall kommt nun der zweite Trigger trg_ddl_util_after ins Spiel. Dieser prüft ob ein Create-Statement ausgeführt wurde. Wenn das der Fall war, wird das neu erstellte Datenbankobjekt automatisch ausgecheckt und der DDL-Extrakt archiviert.

Bei einem Drop-Befehl verhält es sich etwas anders. Hier existiert das Objekt zum Ausführungszeitpunkt ( AFTER ) des Triggers nicht mehr. Es muss aber noch aus der Tabelle ddl_objet_state entfernt werden.

Abbildung 1: Übersicht ddl_util

In Abbildung 1 sehen Sie alle beschriebenen Objekte.

Drop …

Jetzt habe ich der Einfachheit halber Ihnen noch ein kurzes Skript hier eingefügt, mit dem Sie die Tabellen, Trigger und das Package wieder los werden können.


DROP TRIGGER trg_ddl_util_before;
DROP TRIGGER trg_ddl_util_after;
DROP PACKAGE ddl_util;
DROP TABLE ddl_object_state;
DROP TABLE ddl_object_state_hist;

Beispiel

Im Folgenden möchte ich Ihnen die Nutzung der beschrieben Methode anhand einiger kleiner Beispiele demonstrieren. Dazu wurden in dem Schema MY_PROJECTS die beiden Tabellen, das Package und die beiden Trigger angelegt. diesen Account wurde eine Verbindung im SQL Developer hergestellt.

Dann gibt es einen weiteren Account namens DEVELOPER1, welcher mit DBA-Rechten versehen wurde. Der DEVELOPER1  ist via. sqlplus mit der Datenbank verbunden.

Tabelle T1 anlegen

Zu Beginn soll eine Tabelle erzeugt werden.

Abbildung 2: Tabelle T1 anlegen

Wie Sie der Abbildung 2 entnehmen können, wurde die Tabelle T1 ohne Probleme erzeugt. Dabei wurde der Trigger trg_ddl_util_after ausgeführt und ein Lock-Eintrag in der Tabelle ddl_object_state eingefügt.

Abbildung 3: Datenbanktabelle T1 für ausgecheckt

Tabelle T2 erweitern

Jetzt soll der DEVELOPER1 dieser Tabelle eine neue Spalte hinzufügen.

Abbildung 4 : Spalte in T1 ergänzen

Wie zu erwarten war, wird dieser Vorgang mit der Exception ORA-20001 durch den Trigger trg_ddl_util_before unterbunden. Der Meldung können Sie entnehmen, dass die Tabelle durch den User MY_PROJECTS am 17.06.2018 um 20:59:17 gesperrt wurde.

Abbildung 5: Objekt einchecken

Erst wenn der Benutzer MY_PROJECTS die Tabelle wieder zur Bearbeitung frei gibt, kann der DEVELOPER1 die Spalte ergänzen.

Abbildung 6: Spalte hinzufügen

Dies hat wiederum zu Folge, dass die Tabelle jetzt durch den User DEVELOPER1 ausgecheckt wurde.

Tabelle T1 löschen

Setzt nun der User MY_PROJECTS eine Drop Anweisung für die Tabelle T1 ab, wird auch diese mit einer entsprechenden Fehlermeldung abgebrochen.

Abbildung 7: Drop der Tabelle nicht möglich

Möchten Sie jetzt aber die Tabelle trotzdem löschen und die Sperre durch den DEVELOPER1 übergehen, so können Sie das durch den Aufruf der Prozedur unlock_objects mit folgenden Parametern tun.


BEGIN
  DDL_UTIL.UNLOCK_OBJECT(
         P_OBJECT_TYPE = 'TABLE',
         P_OWNER =       'MY_PROJECTS',
         P_NAME =        'T1',
         P_TRACE_HIST =  true,
         P_SECURE_MODE = false
        );
   commit;
END;

Änderungshistorie

Die Trigger bzw. das PL/SQL Package ddl_util sorgen beim Ein- und Auschecken der Datenbankobjekte auch dafür, dass ein DDL-Extrakt in der Tabelle ddl_object_state_hist archiviert wird. So können Sie auf einfache Weise auf ältere Entwicklungsstände zugreifen.

Abbildung 8: Änderungshistorie

Download

Abschließend habe ich Ihnen ein DDL-Script zum Anlegen der beschriebenen Objekte an diesen Beitrag angefügt. Die Verwendung erfolgt aber auf eigene Gefahr.

Oracle-Datenbank: Teamwork mit Bordmitteln Download ddl_util.sql

Dieses DDL-Sript ist im Rahmen des Beitrags Oracle-Datenbank: Teamwork mit Boardmitteln entstanden.

Oracle Datenbankentwicklung: DDL-Trigger zur Auditierung von Strukturänderungen

Oracle bietet mit sogenannten DDL-Triggern eine einfache Möglichkeit Änderungen an Datenbankobjekten zu protokollieren. Eine nützliche Methode für Entwicklerteams.

Eine automatische Auditierung von DDL (Data Definition Language) -Aktionen in einem Datenbank-Schema ist in vielerlei Situationen nützlich. Z.B. können Sie auf diese Weise in einer Entwicklungsumgebung alle Strukturänderungen, die nach einem bestimmten Zeitpunkt erfolgt sind, nachvollziehen. Auf Basis so eines Protokolls sind Sie dann beispielsweise in der Lage ein Upgrade-Skript zu erstellen, mit dem Sie anschließend das produktive System aktualisieren können. Auch aus Sicherheitsgründen kann es sinnvoll sein, strukturelle Änderungen an einer Datenbank zu protokollieren.

Das Thema der DDL- oder auch Systemtrigger ist umpfangreich, soll hier aber nicht in Gänze betrachtet werden. In diesem Beitrag geht es um die Entwicklung  eines Auditierungsmechanismusses, der alle Änderungen an einem Schema protokolliert. Das sind vor allem create, alter, drop Befehle.

Eine Oracle-Datenbank bietet verschiedene Möglichkeiten der Auditierung. Die hier vorgestellte Variante ist aber recht leichtgewichtig und bietet durch ihre Anpassbarkeit viele Gestaltungsmöglichkeiten. So können Sie beispielsweise auch eine DSGVO angepasste, datensparsame  Variante erstellen.

Die dargestellten Beispiele sind mit viel Sorgfalt entstanden, allerdings übernehme ich keine Gewähr für etwaige Fehler oder Probleme, die bei der Nutzung auftreten. Bitte prüfen Sie selber die Beispiele vor der Nutzung.

Datenmodell

Das Datenmodell für dieses Szenario besteht aus zwei Tabellen.

Die Tabelle ddl_log nimmt für jede Änderung der Datenbankobjekte deren Name und Besitzer, den Typ des Objektes, den Zeitpunkt der Änderung und die Art der Änderung auf. Auch die Benutzerdaten sowie das Programm, mit dem die Änderung durchgeführt wurde, werden vermerkt. Zur Erzeugung eines künstlicher Schlüssel wird später eine Sequence verwendet.

Die zweite Tabelle ddl_log_sql ergänzt diese Information um das eigentliche DDL-Statement, welches  ausgeführt wurde.

CREATE TABLE ddl_log
(
  event_id NUMBER (10, 0),
  event_date DATE,
  user_id VARCHAR2 (30 BYTE),
  object_name VARCHAR2 (30 BYTE),
  owner VARCHAR2 (30 BYTE),
  object_type VARCHAR2 (30 BYTE),
  system_event VARCHAR2 (30 BYTE),
  machine VARCHAR2 (64 BYTE),
  program VARCHAR2 (64 BYTE)
)
/

CREATE TABLE ddl_log_sql
(
  event_id NUMBER (10, 0),
  sqlline NUMBER (10, 0),
  sqltext VARCHAR2 (4000 BYTE)
)
/

CREATE SEQUENCE ddl_log_id INCREMENT BY 1
  START WITH 1
  MINVALUE 1
  MAXVALUE 9999999999999999999999999999
  NOCYCLE
  ORDER
  CACHE 20
/

Listing 1: Datenmodell anlegen

Mit Listing 1 können Sie die beschriebenen Objekte anlegen. Dies sollten Sie in dem Schema tun, in dem Sie die Änderungen protokollieren möchten. Alternativ können Sie aber auch ein eigenes Schema dafür definieren. Allerdings müssen Sie dann den Schema-Namen im Quellcode des Triggers ergänzen und denn Tabellen sowie der Sequence voranstellen.

DDL-Trigger

Ein DDL-Trigger wird anders als ein DML-Trigger bei DDL- bzw. Systemereignissen ausgeführt. Das können z.B. Strukturänderungen wie das Erstellen einer Tabelle oder eines PL/SQL Packages, das Droppen einer Tabelle oder das Anlegen einer View aber auch das Anmelden eines Benutzers sein.

Der Ausführungspunkt so eines Triggers kann vor (BEFORE) oder nach (AFTER) der Abarbeitung des DDL-Statements festgelegt werden.

Sie können einen DDL-Trigger global für die komplette Datenbank (DATABASE) oder nur für ein bestimmtes SCHEMA definieren.

Einen DDL-Trigger, der alle Strukturellen Änderungen an Ihrem Schema protokolliert, können Sie – wie in Listing 2 gezeigt – erzeugen. Führen Sie dazu das Skript mit einem Datenbanktool (sqlplus, SQL Developer, APEX SQL Workshop,…) aus. Melden Sie sich dabei mit dem SCHEMA Account an.

Tauschen Sie das keyword SCHEMA durch DATABASE, so wird der Trigger bei allen Änderungen an der gesamten Datenbank gefeuert. Aber Achtung, Sie müssen dafür dann das Schema, in denen sich die verwendeten Tabellen befinden, in dem Trigger hinzufügen.


CREATE OR REPLACE TRIGGER trg_ddl_log
 AFTER DDL
 ON SCHEMA
DECLARE
  l_sql ora_name_list_t;
  l_id NUMBER (10, 0);
BEGIN
  SELECT ddl_log_id.NEXTVAL INTO l_id FROM DUAL;

  INSERT INTO ddl_log (event_id,
    event_date,
    user_id,
    object_name,
    owner,
    object_type,
    system_event,
    machine,
    program)
  (SELECT l_id,
          SYSDATE,
          ora_login_user,
          ora_dict_obj_name,
          ora_dict_obj_owner,
          ora_dict_obj_type,
          ora_sysevent,
          UPPER (SYS_CONTEXT ('USERENV', 'TERMINAL')),
          SYS_CONTEXT ('USERENV', 'MODULE')
       FROM DUAL);

FOR l IN 1 .. ora_sql_txt (l_sql)
  LOOP
    INSERT INTO ddl_log_sql (event_id, sqlline, sqltext)
      VALUES (l_id, l, l_sql (l));
  END LOOP;
END;
/

Listing 2: Create DDL-Audit-Trigger

Der Trigger wird nach allen Änderungen, die an Objekten des jeweiligen Schemas ausgeführt werden, gefeuert.

Zu Begin des Triggers wird mit Hilfe der Sequence ddl_log_id ein künstlicher Schlüssel erzeugt und in der Variable l_id gespeichert.

Das erste Insert-Statement protokolliert die Art der Änderung in der Tabelle ddl_log. Dabei wird dem Datensatz die zuerst erzeugte ID aus der Variablen l_id als Primärschlüssel zugeordnet. Hier werden neben dem Zeitpunkt der Änderung und das geänderte Datenbankobjekt, auch die Benutzerdaten und das verwendete Programm ermittelt und gespeichert.

Im nächsten Schritt wird das DDL-Statement, welches zur Ausführung des Triggers geführt hat, zeilenweise in der Tabelle ddl_log_sql eingefügt. Jeder Zeile wird neben einer Nummerierung auch die l_id als Fremdschlüssel zugeordnet.

Sollten Sie auf die persohnenpersonen Daten – wie den Benutzername und den Maschinennamen des Clients – verzichten wollen, können Sie den Trigger in einer DSGVO freundlicheren Version mit dem Listing 3 anlegen. Dabei werden diese beiden Spalten einfach mit null vorbelegt.


CREATE OR REPLACE TRIGGER trg_ddl_log
 AFTER DDL
 ON SCHEMA
DECLARE
  l_sql ora_name_list_t;
  l_id NUMBER (10, 0);
BEGIN
  SELECT ddl_log_id.NEXTVAL INTO l_id FROM DUAL;

  INSERT INTO ddl_log (event_id,
    event_date,
    user_id,
    object_name,
    owner,
    object_type,
    system_event,
    machine,
    program)
  (SELECT l_id,
          SYSDATE,
          null,
          ora_dict_obj_name,
          ora_dict_obj_owner,
          ora_dict_obj_type,
          ora_sysevent,
          null,
          SYS_CONTEXT ('USERENV', 'MODULE')
       FROM DUAL);

FOR l IN 1 .. ora_sql_txt (l_sql)
  LOOP
    INSERT INTO ddl_log_sql (event_id, sqlline, sqltext)
      VALUES (l_id, l, l_sql (l));
  END LOOP;
END;
/

Listing 3: DSGVO freundlicherer DDL-Audit-Trigger

Möchten Sie den Trigger inklusive der zugehörigen Datenbankobjekte aus Ihrem Schema entfernen, können Sie dafür das Listing 4 nutzen. Wichtig dabei ist die vorgegebene Ausführungsreihenfolge. Würden Sie die Tabellen zuerst zu droppen, so würden Sie dem DDL-Trigger seine abhängigen Objekte entziehen. Anschließend würde der Trigger invalid und zu einem Fehler führen.


DROP TRIGGER trg_ddl_log;
DROP TABLE ddl_log;
DROP TABLE ddl_log_sql;
DROP SEQUENCE ddl_log_id;

Listing 4: Trigger und Tabellen löschen

Generell sollten Sie bei der Verwendung von DDL-Triggern etwas Vorsicht walten lassen, da Sie bei fehlerhafter Verwendung, sich quasi bestimmter Datenbank-Funktionalitäten entledigen könnten.

Anwendungsbeispiele

Ich habe in dem Beitrag Oracle Application Express – Quick SQL bespielhaft ein einfaches Datenmodell für eine Projektverwaltung erstellt. Das zugehörige DDL-Skript habe ich exemplarisch ausgeführt und mit Hilfe des DDL-Audit-Triggers aus Listing 3 protokollieren lassen.

Im Folgenden finden Sie einige einfachen select-Anweisungen, mit deren Hilfe Sie verschiedene Fragestellungen beantworten können.

Wie Sie der Abbildung 1 entnehmen können, wurden folgende Änderungen an der Datenbank vorgenommen.

Abbildung 1: DDL-Log Änderungshistorie

Wenn Sie sich dieses Log ansehen möchten, genügt folgende select-Statement Anweisung:


select event_id,
       event_date,
       user_id,
       object_name,
       owner,
       object_type,
       system_event,
       machine,
       program
 from ddl_log
  order by event_id

Listing 5: Änderungshistorie anzeigen

Das konkrete DDL-Statement, dass für Anlegen der Tabelle projects verantwortlich war,  wurde in der Tabelle ddl_log_sql protokolliert. Eine kleine Unschönheit bringt dieses Protokoll mit sich. Bitte achten Sie darauf, dass die zeilenweise Speicherung teilweise mitten in einer SQL-Anweisung die Zeile umbricht. Dies muss bei der Rekonstruktion des Skriptes beseitigt werden.

Abbildung 2: DDL-Statement

Dieses läßt sich für einen einzelnen Schritt – wie in Listing 6 gezeigt – ermitteln.


select event_id, 
       event_date, 
       object_type,
       sqltext
from ddl_log_sql 
  where event_id = 2
 order by sqlline

Listing 6: DDL-Anweisungen für event_id =2 ermitteln

Interessieren Sie sich für die Änderungen,  die zwischen zwei Releaseständen Ihres Datenbank-Schemas  durchgeführt wurden, können Sie die select-Anweisungen um folgende where Klausel ergänzen. Alternativ können Sie auch die Einschränkungen über den Zeitstempel vornehmen.


select l.event_id, 
       event_date, 
       object_type,
       sqltext
from ddl_log l, 
     ddl_log_sql s
where l.event_id = s.event_id
  and l.event_id > 2 and l.event_id < 10
order by l.event_id, 
         sqlline

Listing 7: Alle DDL-Anweisungen die nach event_id 2 bis zur event_id 5 ausgeführt wurden

Auf Basis so eines Protokolls, können Sie anschließend ein Upgrade-Skript erarbeiten oder die Release-Notes für die neue Softwareversion ergänzen.

Auch die Änderungen an einem bestimmten Datenbankobjekt, hier der Tabelle TO_DOS, können nachvollzogen werden. Dazu finden Sie in Listing 8 ein einfaches Beispiel.


select l.event_id , 
       l.object_name, 
       event_date, 
       object_type,  
       sqltext
from ddl_log l, 
     ddl_log_sql s
where l.event_id = s.event_id
  and l.object_name = 'TO_DOS'
order by l.event_id, 
         sqlline

Listing 8: DDL-Statement zum Erstellen der Tabelle TO_DOS ermitteln

So einen DDL-Audit-Trigger ist gerade auch für Entwicklerteams hilfreich. Hier ist für alle Programmierer leicht nachvollziehbar, welche Änderungen an dem Datenmodell durchgeführt wurden. Auch zum prüfen von Installationsvorgängen lässt sich die Protokollierung einsetzen.

Wie Sie meine ScreenShots entnehmen konnten, setzte ich diesen Trigger auch gerne bei der Entwicklung von APEX-Anwendungen ein. Gerade durch die Beschränkung seiner Ausführung auf die Änderungen eines Datenbankschemas können so geziehlt alle Entwicklungsschritte auf Datenbankebene nachvollzogen werden. Auf Basis der Tabelle ddl_log_sql lassen sich dann Scripte für die Supported Objects erstellen.