Praktische Anwendungsentwicklung mit Oracle Forms ... · Oracle Forms 6i ist die letzte Version,...

39
Praktische Anwendungsentwicklung mit Oracle Forms von Perry Pakull, Stefan Jüssen, Walter H. Müller 1. Auflage Hanser München 2007 Verlag C.H. Beck im Internet: www.beck.de ISBN 978 3 446 41098 5 Zu Inhaltsverzeichnis schnell und portofrei erhältlich bei beck-shop.de DIE FACHBUCHHANDLUNG

Transcript of Praktische Anwendungsentwicklung mit Oracle Forms ... · Oracle Forms 6i ist die letzte Version,...

Praktische Anwendungsentwicklung mit Oracle Forms

vonPerry Pakull, Stefan Jüssen, Walter H. Müller

1. Auflage

Hanser München 2007

Verlag C.H. Beck im Internet:www.beck.de

ISBN 978 3 446 41098 5

Zu Inhaltsverzeichnis

schnell und portofrei erhältlich bei beck-shop.de DIE FACHBUCHHANDLUNG

Perry Pakull, Stefan Jüssen, Walter Müller

PraktischeAnwendungsentwicklung

mit Oracle Forms

ISBN-10: 3-446-41098-8ISBN-13: 978-3-446-41098-5

Leseprobe

Weitere Informationen oder Bestellungen unterhttp://www.hanser.de/978-3-446-41098-5

sowie im Buchhandel

1

1 1 Grundlagen

In diesem Kapitel geben wir einen ersten Überblick über Oracle Forms, angefangen von der Entwicklungsumgebung mit den einzelnen Funktionen zur Erstellung und Bearbeitung von Forms-Modulen bis hin zur Laufzeitumgebung Forms Services. Details und Vertie-fungen dazu folgen dann in den späteren Kapiteln. Für den Entwickler sind Grundlagen sehr wichtig, egal ob Einsteiger oder Fortgeschrittene. Der erfahrene Entwickler wird hier vermutlich keine wirklich neuen Erkenntnisse gewinnen, wohl aber das eine oder andere Detail zu seinem Wissen ergänzen. Für den Neu- oder Quereinsteiger stellt dieses Kapitel eine komprimierte Einführung in die Oracle-Forms-Programmierung dar. Das Kapitel beinhaltet folgende Themen: Welche Bedeutung hat Oracle Forms? Wofür wird Oracle Forms eingesetzt? Was ist Oracle Forms? Welche Möglichkeiten bietet Oracle Forms Developer für den Entwickler? Was sind typische Formulare und wie werden sie erstellt? Welche Standardfunktionalität bietet Oracle Forms? Was gehört zu einem Forms-Modul?

1.1 Oracle Forms

1.1.1 Welche Bedeutung hat Oracle Forms?

Oracle Forms gehört zusammen mit Oracle Reports und Oracle Designer in die Kategorie der traditionellen Entwicklungswerkzeuge von Oracle. Die ersten Versionen von Oracle Forms (damals noch SQL*Forms) waren zeichenorientierte Maskensysteme für einfarbige Terminals. Mit der Version 4 wurde dann erstmals eine grafische Benutzeroberfläche für Client-Server-Architekturen vorgestellt. Die Version 6i war nicht nur als Client-Server-Variante, sondern auch als Web-Applikation mit einer Mehrschichtarchitektur verfügbar.

1 Grundlagen

2

Im September 2005 definierte Oracle in dem offiziellen Dokument „Oracle Forms – Oracle Reports – Oracle Designer Statement of Direction“ [TSOD05] die künftige Strategie für diese Produkte. Aus diesem Artikel haben wir folgende wichtige Punkte zusammengefasst: Entwicklungsstrategie Die Entwicklungswerkzeuge sind ausgelegt für die Entwicklung von Enterprise-

Applikationen basierend auf einer Mehrschichtarchitektur. Die vorhandenen Produkte wurden in die Kategorien „traditionelle Entwicklungs-

werkzeuge“ und „Java-Entwicklungswerkzeuge“ eingeteilt. Zu den traditionellen Entwicklungswerkzeugen gehören, wie bereits erwähnt, Oracle Forms, Oracle Re-ports und Oracle Designer. Für die Java-Entwicklung steht Oracle JDeveloper zur Verfügung. Alle Entwicklungswerkzeuge einschließlich JDeveloper sind als Pro-duktgruppe in der Oracle Developer Suite verfügbar. Oracle Application Server wurde die einzige mögliche Laufzeitumgebung für alle

Applikationen, basierend auf Oracle Entwicklungswerkzeugen. Oracle Forms ist kein eigenständiges Produkt mehr, sondern der Betrieb ist nur mit

einer Oracle-Application-Server-Lizenz möglich. Seit Oracle Forms 9i können Forms-Applikationen nur noch als Web-Applikationen auf dem Oracle Application Server betrieben werden. Die Oracle-Produkte Developer Suite und Application Server sind dadurch eng miteinander verbunden. Die Weiterentwicklung der Software sieht kleinere Entwicklungsschritte für Oracle

Forms, Oracle Reports und Oracle Designer mit verbesserter Integration in Oracle Application Server und Oracle Datenbank vor. Im Vordergrund stehen nicht neue Funktionen der Software, sondern mehr Stabilität, Offenheit für Standardtechnolo-gien wie Java und XML sowie eine bessere Integration mit der Oracle-Application-Server-Umgebung. Die Installation von Forms und Reports Services war für Oracle Application Server

9i nur als Enterprise Edition möglich. Die Enterprise Edition erforderte die Installa-tion einer Infrastruktur und eines Middle Tiers. Oracle Application Server 10g ent-hält erstmals eine spezifische Installationsvariante für Forms und Reports Services ohne Infrastruktur. Die neue Software für Oracle Developer Suite und Oracle Application Server ist

fokussiert auf die bestmögliche Unterstützung für die Migration und das Upgrade von Oracle-Applikationen.

Support der Produkte Der Support für Oracle Forms und Oracle Reports wird bis 2013 garantiert unab-

hängig davon, welche Version von Forms, Reports, der Developer Suite oder vom Application Server gerade aktuell sein sollte. Oracle Forms 6i ist die letzte Version, die Client-Server-Anwendungen und Charac-

ter-Mode-Anwendungen unterstützt. Der erweiterte Support endet am 31.01.2008.

1.1 Oracle Forms

3

Technische Empfehlungen für die Kunden Im Vordergrund steht die Migration der bestehenden Oracle-Applikationen in die

neue Mehrschichtarchitektur, basierend auf Oracle Application Server, sowie ein Upgrade auf die jeweils neueste Oracle-Version. Oracle Application Server ist die Integrationsplattform für alle Applikationen basie-

rend auf Oracle-Entwicklungsprodukten. Dadurch besteht die Möglichkeit, Oracle-Forms-Applikationen und Oracle-ADF-Applikationen miteinander zu verbinden. Durch den Java Importer erweitern sich die Integrationsmöglichkeiten von Oracle Forms, so dass auch andere Java-Applikationen und andere Application Server mit einbezogen werden können. Für die Entwicklung von neuen Applikationen stehen sowohl die traditionellen als

auch die Java-Entwicklungswerkzeuge zur Verfügung. Beide können je nach An-forderung entsprechend eingesetzt werden, wobei die Fokussierung eindeutig im Java/J2EE-Umfeld liegt. Das Ziel bei den Java-Entwicklungswerkzeugen JDeveloper und ADF ist es, den

Rapid-Application-Development-Ansatz der traditionellen Entwicklungswerkzeuge zu übernehmen und damit dem Oracle-Entwickler eine homogene Umgebung so-wohl für die Forms- als auch für die Java-Welt zu bieten.

Es hat in der Vergangenheit viele Diskussionen über diesen Artikel gegeben.1 Der Verzicht auf die weitere Unterstützung der Client-Server-Architektur und die strategische und kon-sequente Neuausrichtung auf Mehrschichtarchitekturen sowie die Fokussierung auf J2EE-Technologien haben nicht nur Forms-Kunden, sondern auch Forms-Entwickler verunsi-chert, was die Zukunft der bestehenden Forms-Applikationen betrifft [Arch07]. Doch seitens Oracle ist der Weg in die Zukunft klar vorgegeben: Migration der bestehen-den Forms-Applikationen auf die neue Architektur, Upgrade auf die neuesten Versionen und Integration mit neuen Technologien. Aus unserer Sicht bietet dieser Weg die besten Möglichkeiten, vorhandene Applikationen weiterhin sinnvoll einzusetzen. Die neue Architektur stellt nicht nur eine Weiche in der weiteren Entwicklung von Oracle Forms dar, sondern zieht eine klare Trennlinie, so schmerzhaft das für Client-Server-Anwendungen auch sein mag. Die Herausforderung für bestehende Forms-Applikationen ist die Umstellung auf die neue Mehrschichtarchitektur und die Integration mit anderen Technologien wie J2EE und .NET.

„Haben Sie keine Angst, einen großen Schritt zu machen. Man kann einen Abgrund nicht in zwei kleinen Sprüngen überqueren.“

David Lloyd George (1863–1945)

In diesem Buch wollen wir zeigen, dass die aktuelle Version von Oracle Forms das Poten-zial hat, moderne und leistungsfähige Applikationen im Oracle-Umfeld zu entwickeln. „Modern“, weil die Mehrschichtarchitektur und die Öffnung für Java-Technologien den 1 Die Blogs von Wilfred van der Deijl (OraTransplant) und Grant Ronald liefern spannende und anre-

gende Beiträge aus aller Welt zum Thema Oracle Forms.

1 Grundlagen

4

Anforderungen der aktuellen Software-Architektur entspricht. Leistungsfähig – weil der deklarative und Datenbank-getriebene Ansatz von Oracle Forms die Anforderungen an komplexe Benutzeroberflächen optimal abdeckt. Das Potenzial liegt jedoch nicht nur in der Technik. Der jahrelange Einsatz der Software in vielen kleinen und großen Applikationen in den verschiedensten Branchen und die Anzahl der verfügbaren Entwickler mit Forms-Know-how machen Oracle Forms zu einer bewährten Technologie. Die Kombination aus Technik und Erfahrung macht Oracle Forms zu einem sehr produktiven Werkzeug.

1.1.2 Was ist Oracle Forms?

Oracle Forms ist eine Sprache der vierten Generation, spezialisiert auf die Erstellung von Formularen basierend auf einer Oracle Datenbank. Ein Formular wird anhand der Attribute und Regeln einer Datenbanktabelle erzeugt. Für die Anbindung der Tabelle und die Defi-nitionen des Layouts sind deklarierbare Strukturelemente in Oracle Forms vorhanden. Die Elemente sind eingebettet in einen standardisierten Rahmen von Funktionen und Abläufen, der durch Ereignisse gesteuert wird. Die Programmiersprache für Oracle Forms ist PL/SQL. Neben unserer Definition gibt es eine Reihe von allgemeinen Aussagen zum Thema Oracle Forms und 4GL, die wir hier ergänzen möchten.

Fourth Generation Language „Fourth generation language“ – oder kurz 4GL – bezeichnet Programmiersprachen der vierten Generation. Diese sind darauf ausgerichtet, rasch für einen bestimmten Anwen-dungsbereich (das heißt mit möglichst wenigen Code-Zeilen) Funktionen oder komplette Anwendungen schreiben zu können. Der Begriff 4GL ist nicht exakt definierbar und wird vor allem für Marketing-Zwecke ein-gesetzt. Gemeinsames Hauptziel aller 4GL ist es jedoch, im Vergleich mit Sprachen der dritten Generation dieselbe Funktionalität mit weniger Code zu erreichen. Der Begriff wurde in den 1980er Jahren häufig verwendet. Heute wird auch der Begriff Rapid Applica-tion Development (RAD) mit ähnlicher Semantik angewandt. In Sprachen der dritten Generation stand die Einführung von standardisierten Kontroll-strukturen im Vordergrund. Bei Sprachen der vierten Generation liegen zusätzlich Baustei-ne vorgefertigt vor, die häufig in spezialisierten Anwendungen vorkommen. Nicht mehr wie ein Problem gelöst wird, steht im Vordergrund, sondern was der Rechner machen muss, um dieses Problem zu lösen. Die Bezeichnung 4GL wurde stark von James Martin propagiert. Er benutzte sie in seinem Buch „Application Development Without Programmers“ [Mart81]. Trotzdem würde sich die Mehrheit der Anwender von 4GL-Sprachen selbst doch als Programmierer bezeichnen, und die meisten 4GL-Sprachen enthalten auch eine Möglichkeit, um Programmierlogik in einer meist proprietären Drittgenerationssprache zu beschreiben. Nicht alle 4GL sind bzw. waren erfolgreich. Wenn aber eine Sprache der vierten Generati-on für den Zweck eingesetzt wird, für den sie gebaut ist, gab und gibt es auch spektakuläre Erfolge [Wikipedia, 4GL].

1.1 Oracle Forms

5

Gemäß Software Productivity Research gehört Oracle Forms zu den produktivsten 4GL-Sprachen überhaupt [SPR05]. Oracle selbst spricht heute nicht mehr von 4GL, sondern verwendet in Dokumenten den Begriff Rapid Application Development, um die Produktivität gegenüber Sprachen der dritten Generation zu betonen.

Rapid Application Development Was bedeutet Rapid Application Development eigentlich bei Oracle Forms? Produktivität wird heute von jedem Entwicklungssystem erwartet; was aber macht ein System pro-duktiv? Der Forms-Entwickler kann deklarativ sehr schnell und einfach ein lauffähiges Programm erzeugen, das heißt, er beschreibt, was sein Programm tun soll. Ein einfaches Formular, basierend auf einer Tabelle in einer Oracle Datenbank, kann mit den Wizards in Sekunden erstellt werden. Der Entwickler muss im Wesentlichen nur den Namen der Tabelle in der Datenbank und die Art des Formulars angeben, und die Wizards erzeugen automatisch die dafür notwendigen Objekte im Form-Modul. Die Funktionalität für das Formular – Daten suchen, anzeigen, einfügen, ändern und löschen – ist bereits vorhanden. Der Entwickler konzentriert sich auf das „Was“ und nicht auf das „Wie“.

Prototyping Die einfache Erstellung lauffähiger Anwendungen ermöglicht die Entwicklung von Proto-typen für Applikationen oder Applikationsteile. Der spätere Benutzer kann damit bereits sehr früh auf die erzeugten Formulare zugreifen und den Entwicklungsprozess beeinflussen.

Fourth Generation Environment Als „Fourth generation environment“ kann man die Kombination von Oracle Designer und Oracle Forms ansehen. Oracle Designer bietet als CASE-Werkzeug nicht nur die Möglich-keit, Datenmodelle aus Metadaten zu generieren, sondern auch die dazugehörigen Anwen-dungen, wie zum Beispiel Formulare für Oracle Forms.

1.1.3 Einsatzbereiche von Oracle Forms

Schon die allgemeinen Aussagen zu den 4GL-Werkzeugen deuten an, dass spezialisierte Werkzeuge wie Oracle Forms nur dort erfolgreich eingesetzt werden können, wo sie ent-sprechend ihrer Spezialisierung genutzt werden. Die Spezialisierung auf formularbasierte und datenintensive Benutzeroberflächen prädestiniert Oracle Forms für kommerzielle Informationssysteme. Dazu gehören Standard- oder Individuallösungen zur Abbildung von Geschäftsprozessen einzelner Unternehmen, wie zum Beispiel Warenwirtschaftssysteme, Stammdatenverwaltungen, Call-Center-Anwendungen usw. [Star05]. Dabei steht der Bezug zu einer Oracle Datenbank im Vordergrund. Eine der wichtigsten Voraussetzungen für den Einsatz von Oracle Forms ist die Verwendung von Oracle Datenbanken. Die Ent-wicklungswerkzeuge von Oracle bieten sicherlich die besten Entwicklungs- und Integra-tionsmöglichkeiten mit einer Oracle Datenbank.

4.1 Grundlagen

83

4 4 Daten

Dieses Kapitel befasst sich mit der Datenversorgung und der Datenentsorgung eines For-mulars. Primär fragen wir, woher Oracle Forms die Daten holt und was mit den Änderun-gen durch den Anwender geschieht. Die Themen dieses Kapitels sind auch für den fort-geschrittenen Entwickler interessant. Unter anderem geht es um die Beziehung zwischen Block und Tabelle List-Items, LOVs und Record Groups Globals, aber welche implizit verwendete Cursor Rowid oder nicht Extras bei Insert und Update Namensauflösung, der Wegweiser für Objekte die dringende Empfehlung: Views interessante Block Properties Forms und Concurrency

4.1 Grundlagen

Ein Forms-Modul besteht aus einem oder mehreren Data Blocks und jeder Data Block aus einer Menge von Datensätzen oder Zeilen. Jeder Datensatz umfasst eine Menge von Items. Einem Data Block ist eine Tabelle (oder View) der Datenbank zugeordnet. Ein Datensatz repräsentiert einen Record dieser Tabelle. Ein Item zeigt uns den Inhalt einer Column eines Records der Tabelle. Oracle Forms stellt einen Satz von Standardfunktionen für das An-zeigen, Suchen, Ändern, Erstellen und Löschen von Datensätzen bereit. Das Ändern des Inhalts eines Items entspricht einer Änderung des Inhalts der Spalte einer Tabelle. Selbst-verständlich nutzt Oracle Forms darüber hinaus weitestgehend die Funktionen der Daten-bank, wie Transaktionslogik, Zugriffsschutz, Aufruf von PL/SQL, usw. Bei der Herstellung (Programmierung) eines Formulars legen wir in einem ersten, deklara-tiven Schritt Instanzen von vordefinierten Forms-Objekten an und steuern deren Verhalten

4 Daten

84

durch Setzen von Properties (Forms erlaubt es uns nicht, eigene Objekte zu definieren oder die Struktur der Objekte zu ändern, indem wir beispielsweise neue Properties hinzufügen. Genauso wenig können wir – bis auf eine Ausnahme – Objektinstanzen zur Laufzeit eines Formulars dynamisch erzeugen). In einem zweiten Schritt können wir das so definierte Standardverhalten über Trigger mit selbst geschriebenem PL/SQL Code ergänzen. Dabei steht uns eine große Menge von Built-Ins für die Behandlung der Objekte zur Verfügung.

4.2 Ein Beispiel

Im Folgenden erzeugen wir ein einfaches Formular unter ausschließlicher Verwendung der Wizards. Dieses Formular soll uns einige Spalten der Tabelle EMP anzeigen und zur Ände-rung anbieten. Die Schritte im Einzelnen: Als Vorarbeit richten wir uns auf dem Desktop unseres Rechners einen neuen Shortcut für den Forms Builder ein und setzen dessen Properties (siehe Abbildung 4.1).

Abbildung 4.1 Shortcut Properties für den Aufruf des Form Builders

Mit einem Doppelklick auf diesen Shortcut starten wir den Forms Builder. Da der Parame-ter USERID geeignet gesetzt ist, werden wir damit auch gegen das gewünschte Schema in der richtigen Datenbank angemeldet. Im bereits automatisch angelegten leeren Formular namens MODUL1 starten wir über das Menü den Block Wizard (siehe Abbildung 4.2).

4.2 Ein Beispiel

85

Abbildung 4.2 Auswahl des Data Block Wizard

Im nächsten Schritt bestätigen wir die Voreinstellung, dass dieser neue Block auf einer "Table or View" basieren soll. Im folgenden Bild geben wir zunächst den Namen der Tabelle EMP an. Nach Betätigen des Buttons "Refresh" sehen wir im Bereich "Available Columns" alle Spalten unserer Tabelle. Die Spalten, die unser Block enthalten soll, stehen im Bereich "Database Items". Über die Buttons ">", ">>", "<<", "<" können wir die Spalten zwischen den beiden Bereichen hin- und herschieben. Das Ergebnis zeigt Abbil-dung 4.3.

Abbildung 4.3 Die Spalten der Tabelle EMP

Im nächsten Schritt übernehmen wir den Blocknamen "EMP" unverändert. Damit haben wir einen Block namens "EMP" erstellt, der auf einer Tabelle EMP basiert und die Items "EMPNO", "ENAME", "HIREDATE", "SAL" und "COMM" enthält. Die restlichen Spal-ten der Tabelle EMP, nämlich "JOB", "MGR" und "DEPTNO", spielen in diesem Block keine Rolle. Somit steht unsere Datenkopplung. Im folgenden Schritt (Abbildung 4.4) lassen wir uns gerne für unsere bisherige Arbeit loben und übernehmen den Defaultwert "… then call the Layout Wizard".

4 Daten

86

Abbildung 4.4 Den Datenblock hätten wir geschafft.

Wir nutzen den automatisch gestarteten Layout Wizard, um ein Layout festzuzurren. Den ersten Schritt übernehmen wir ohne Änderung. Im nächsten Schritt (Abbildung 4.5) legen wir fest, dass alle Items unseres Blocks angezeigt werden sollen. Sonst nehmen wir keine Änderungen vor.

Abbildung 4.5 Wir wollen alles (sehen).

4.2 Ein Beispiel

87

Den nächsten Schritt nutzen wir, um die Beschriftungen unserer Felder auf dem Formular zu ändern (Abbildung 4.6).

Abbildung 4.6 Ein bisschen Outfit

Im folgenden Fenster wählen wir als Darstellungsart "Tabular", um schließlich im letzten Fenster (Abbildung 4.7) eine Überschrift, die Anzahl der Datensätze, deren Abstand und einen Scrollbar zu wählen.

Abbildung 4.7 Der finale Design-Schritt

Wir speichern das Formular mit "Save as", generieren es mit CTRL+T und schauen uns das Ergebnis (Abbildung 4.8) im Browser unserer Wahl an.

4 Daten

88

Abbildung 4.8 Was für ein schönes Formular!

Natürlich gäbe es hier noch einiges zu ändern. Zum Beispiel eine ordentliche Formatierung des Datums, die rechtsbündige Ausrichtung der Feldinhalte "Gehalt" und "Provision" sowie deren Formatierung. Die Feldüberschriften in gleicher Weise wie die Feldinhalte auszurichten, würde unser Wohlgefallen deutlich steigern. Falls uns statt der Fensterüber-schrift "WINDOW1" spontan etwas anderes einfällt und wir den Textbereich der Menü-leiste vielleicht durch eher applikationsspezifische Begriffe ersetzen wollen, ließen sich diese Änderungen mit dem Layout-Editor bewerkstelligen. Doch fürs Erste wollen wir es gut sein lassen. Mit dieser Wizard-gestützten Vorgehensweise haben wir ein voll funktionsfähiges Formu-lar geschaffen, mit dem die Daten einer Tabelle in der Datenbank manipuliert werden können. Im Folgenden wollen wir die Möglichkeiten, die uns Forms Builder für den lesen-den und schreibenden Zugriff auf die Datenbank anbietet, etwas genauer betrachten. Hier sind wir versucht, „erschöpfend“ statt „genauer“ zu schreiben, doch hat uns der langjährige Umgang mit diesem mächtigen Werkzeug Bescheidenheit gelehrt.

4.3 Der lesende Zugriff

Betrachten wir den Standardfall: Ein Block basiert auf einer Tabelle. Als Sonderfall kann es sich hier auch um eine temporäre Tabelle handeln. Statt einer Tabelle können Sie auch eine View angeben – wie wir sehen werden, eine sehr praktische und elegante Vorgehens-weise. Eher exotische Zugänge: Ein Block kann seine Daten über eine PL/SQL-Prozedur beschaffen.

Diese Methode ist relativ aufwendig zu programmieren und verlangt nach einem ge-eigneten Generator. Sie ist nicht zu empfehlen. Wenn die Daten wirklich nur per PL/SQL Code bereitgestellt und/oder gespeichert werden können, sollte man als Alter-native Views, die auf Table Functions basieren, prüfen.

4.3 Der lesende Zugriff

89

from clause (in der Online–Hilfe gelegentlich als sub-query bezeichnet) Als Datenquelle wird hier eine „inline-View“ angegeben – die spätestens nach erfolg-reichem Test durch eine View in der Datenbank ersetzt wird. Eigentlich handelt es sich hierbei um einen Spezialfall, da jedes gültige SQL-Code-Fragment als Datenquelle angegeben werden kann. Forms versucht, daraus einen kom-pletten und gültigen SQL-Befehl abzuleiten (siehe unten). On-Select und On-Fetch

Über diese beiden Trigger lässt sich ein Zugriff auf jede beliebige Datenquelle von Hand ausprogrammieren. Ein Beispielskelett dafür ist in der Online-Hilfe zum On-Fetch Trigger zu finden.

Weitere Zugriffsarten: Control Block, Control Item

Ein Block oder ein Item ohne Zuordnung zu einer Tabelle oder einer Spalte. Wird vor-wiegend zur Speicherung von Zwischenwerten benutzt. Mirror Item

Eine Möglichkeit, zwei oder mehr Items derselben Tabellenspalte zuzuordnen. Forms erledigt die erforderliche Synchronisation zwischen beiden Items (sowohl bei der Anzeige als auch bei der Eingabe). Lookups

Wird beispielsweise verwendet, wenn zur Bankleitzahl der Name des zugehörigen Instituts angezeigt werden soll. Ein Standardeinsatzgebiet für in Packages organisierte serverseitige APIs. Record Groups

Eine Art lokale (aber altertümlich zu programmierende) Tabelle. Hauptverwendungs-zweck dürften LOVs und Hierarchical Trees sein. Siehe Abschnitt 4.3.1. List of Values (LOVs)

Wird eingesetzt, wenn der Anwender aus einer großen Anzahl von möglichen Werten, die in einer geeigneten Tabelle der Datenbank abgelegt sind, den richtigen auswählen soll (siehe Abschnitt 4.3.2). List-Items

Hiermit soll wie bei LOVs die Eingabemenge eingeschränkt werden, wobei die Menge der zulässigen Werte im Vergleich zu LOVs sehr klein ist (siehe Abschnitt 4.3.3). Formelspalten (Formula Items)

Items, die über eine PL/SQL-Funktion oder eine Forms-eigene Summary-Funktion gefüllt werden. Parameter und Parameterlisten

Geeignet für die Übergabe von Daten aus der Umgebung des Forms-Moduls, zum Beispiel von einem aufrufenden Modul oder von der Applikationsumgebung, oder für die Zwischenspeicherung von Daten.

4 Daten

90

Globals Forms Globals genauso wie Package Globals für die Kommunikation zwischen Forms-Modulen und für das Zwischenspeichern von Werten (siehe Abschnitt 4.3.4). Built-In Package TOOL_ENV

Für das Lesen von Umgebungsvariablen. Built-In Package TOOL_RES

Für das Lesen von Einträgen in einer RES-Datei, einer speziell formatierten Binärdatei. Solche Dateien müssen mit dem Tool RSPA aus einer speziell strukturierten Textdatei erzeugt werden und können mit dem Tool RSPR wieder in eine Textdatei zurück-gewandelt werden. (Hier sei allerdings die Frage erlaubt, warum Daten im Dateisystem des Betriebs-systems abgelegt werden sollen, wenn man doch besser eine Datenbank benutzt). Built-In Funktion get_item_property(…, database_value)

Liefert den Inhalt eines Items, der mit der letzten Query aus der Datenbank geholt wurde oder NULL bei einem neuen Satz. Lesen aus einer Datei

Möglich über das Built-In Package TEXT_IO, aber siehe Bemerkung zu TOOL_RES. Die in der Praxis am häufigsten verwendeten Varianten werden in den folgenden Abschnit-ten näher beschrieben.

4.3.1 Record Groups Eine Record Group ist eine Forms-interne Tabelle. Strukturell ähnelt sie einer Tabelle in der Datenbank. Sie existiert nur während der Laufzeit eines Formulars und verschwindet spätestens mit Ende einer Session. Der Zugriff erfolgt über spezielle Built-In-Funktionen. Record Groups werden für die Zwischenablage von Daten, als Basis für LOVs, List-Items und Items vom Typ Hierarchical Tree verwendet. Forms unterscheidet drei Typen von Record Groups: statisch

Eine statische Record Group wird vom Programmierer aufgebaut. Sie kann zur Lauf-zeit nicht geändert werden, d.h., Struktur und Inhalt sind während der gesamten Le-benszeit einer Record Group/Formular unveränderlich. dynamisch, programmiert

Die Record Group wird ausschließlich zur Laufzeit angelegt und gefüllt, d.h., Struktur und Inhalt werden komplett zur Laufzeit des Formulars behandelt. dynamisch, Query-basiert

Die Record Group wird zum Programmierzeitpunkt angelegt und zur Laufzeit gefüllt. Die Struktur wird aus der Select-Liste eines SQL Select Statements abgeleitet, d.h., die Struktur ist fix und der Inhalt wird zur Laufzeit bearbeitet.

Die statische Record Group ist nur von geringem Nutzen. In der Praxis ist sie kaum zu fin-den. Variante 2 – dynamische, programmierte Record Groups – ist einer der wenigen Fälle,

4.3 Der lesende Zugriff

91

wo in Forms ein Objekt zur Laufzeit angelegt werden kann. (Bei Blöcken oder Items zum Beispiel sucht man diese Möglichkeit vergebens.) Variante 3 ist die am häufigsten ver-wendete Art. Die wichtigsten Built-Ins für die beiden dynamischen Typen: DELETE_GROUP löscht eine Record Group (nur Typ 2 ) CREATE_GROUP erstellt eine leere Record Group (nur Typ 2) ADD_GROUP_COLUMN fügt Spalten hinzu (nur Typ 2)

Alternativ zu den beiden obigen Befehlen (CREATE_GROUP und ADD_GROUP_ COLUMN) kann CREATE_GROUP_FROM_QUERY verwendet werden. Dieses Built-In erstellt eine leere Record Group, gibt ihr die Struktur der Select-Liste des als Parameter angegebenen SQL Statements und speichert das SQL Statement bei der Record Group. Für das Füllen und Leeren gibt es die folgenden Verfahren: ADD_GROUP_ROW fügt eine Zeile ein (Typ2 und 3). DELETE_GROUP_ROW löscht eine oder alle Zeilen (Typ 2 und 3). SET_GROUP_xxx_CELL füllt einzelne Spalten; xxx steht für CHAR, DATE oder

NUMBER, je nach Typ der Spalte. Oder die etwas bequemere Form: POPULATE_GROUP löscht den aktuellen Inhalt der Record Group, führt das zuge-

wiesene SQL Statement aus und kopiert dessen Ergebnisse in die Record Group. (Bei Typ2 funktioniert dieser Aufruf natürlich nur, wenn mit CREATE_GROUP_FROM_ QUERY eine Query bereits zugewiesen wurde) POPULATE_GROUP_WITH_QUERY ersetzt das der Record Group zugewiesene SQL

Statement durch das neu angegebene, löscht den Inhalt der Record Group und füllt sie mit dem Ergebnis des neuen SQL Statements. Dieses Built-In lässt die Satzstruktur der Record Group unverändert. Was bedeutet: Das neue SQL Statement muss zur Struktur der Record Group „passen“, d.h., der Datentyp der Spalten muss gleich bleiben oder mindestens durch implizite Typwandlung erreichbar sein: die Länge wird notfalls ge-kürzt. Es müssen alle Spalten, notfalls mit null, gefüllt sein. Die Angabe zusätzlicher Spalten ist möglich, aber nutzlos – sie werden einfach verworfen.

Das Lesen der Daten in einer Record Group erfolgt mit einem der drei Built-Ins GET_ GROUP_xxx_CELL; xxx steht für CHAR, DATE oder NUMBER, je nach Typ der Spalte. Schließlich gibt es noch zwei Sonderfälle für das Füllen einer Record Group: RETRIEVE_LIST kopiert den Inhalt eines List-Items in eine Record Group und POPULATE_GROUP_FROM_TREE kopiert den Inhalt eines (Sub-)Baums eines Items

vom Typ Hierarchical Tree in eine Record Group. In beiden Fällen muss natürlich die Zielgruppe strukturell (Reihenfolge und Typ der Spalten) zur Datenquelle passen.

Bewertung Der einzige wirklich sinnvolle Verwendungszweck für Record Groups ist ihr Einsatz als Datenspeicher für LOVs.

4 Daten

92

4.3.2 List of Values (LOV)

Der Anwender kann als Eingabehilfe ein LOV-Window aufrufen. Dieses modale Fenster enthält Daten, die es ihm erleichtern (sollen) den richtigen gewünschten Wert auszuwäh-len. Ein LOV-Window kann ein oder mehrere Spalten anzeigen und an das rufende Formu-lar zurückgeben. Eine LOV bezieht ihre Daten aus einer Record Group. Mehrere LOVs können auf derselben Record Group basieren. Die LOV ihrerseits wird einem Item zuge-ordnet. Ein und dieselbe LOV kann mehreren Items zugeordnet werden. Zum Anlegen einer LOV sollte in den meisten Fällen ein Wizard benutzt werden. Dabei kann die zugehörige Record Group gleich mit erstellt werden. Die Zuordnung zu einem Item geschieht aber in jedem Fall manuell.

4.3.2.1 Outfit

Wie kann der Anwender erkennen, dass zu einem Item eine LOV hinterlegt ist? Abbildung 4.9 zeigt die beiden markierten Elemente, wie Forms die Existenz einer LOV an der Ober-fläche signalisiert.

Abbildung 4.9 Anzeige der Existenz einer LOV im fertigen Formular

In der Statuszeile sehen wir die so genannte "LOV Lamp", nämlich den Text "List of Valu…". Diese Variante wird standardmäßig angeboten. Voraussetzung ist natürlich, dass die Statuszeile nicht abgeschaltet ist (siehe Property Console Window auf Forms-Ebene). Die andere Methode, drei Punkte im Feld anzuzeigen, wird über den Eintrag "app.ui.lovButtons=true" in der Konfigurationsdatei registry.dat eingestellt. Diese Datei steht auf dem Applikationsserver im Verzeichnis "forms/java/oracle/forms/registry". Die Pünktchen werden nur angezeigt, wenn das Item den Fokus hat (dummerweise wurde ver-gessen, diese Funktionalität auch für den Query-Modus zu programmieren). Gelegentlich sieht man auch die Lösung, neben dem Feld ein Icon vergleichbar dem Dropdown Button bei List-Items anzuzeigen. Neben der Tatsache, dass der Indikator explizit ausprogram-miert werden muss, sorgt der zusätzliche Button für Unruhe auf dem Bild. Diese Form des optischen Rauschens sollten Sie vermeiden. In Abbildung 4.10 sehen wir die zur Laufzeit des Formulars geöffnete LOV. Die mögli-chen Werte sind bereits auf die Managernummer, beginnend mit 778, eingeschränkt (per Autoreduction).

4.3 Der lesende Zugriff

93

Abbildung 4.10 LOV zur Laufzeit

4.3.2.2 Properties im Zusammenhang mit LOVs

In Abbildung 4.11 sehen wir, wie eine LOV einem Item zugeordnet wird.

Abbildung 4.11 Zuordnung der LOV "LOV_MGR:BY_NO" zu einem Item

Abbildung 4.12 zeigt unter der Rubrik „Functional“ die typischen Properties der LOV, ein-schließlich der hinter der Property Column Mapping Properties versteckten Abbildungs-vorschriften.

Abbildung 4.12 Properties der LOV mit ihren Spaltenzuordnungen

4 Daten

94

Abbildung 4.13 Properties der zugehörigen Record Group

In der Abbildung 4.13 werden die Properties der zu obiger LOV gehörenden Record Group dargestellt. Wenn Filter before Display auf Yes gesetzt ist, wird beim Start der LOV die Eingabe einer Einschränkung gefordert, mit der die Anzahl der möglichen Werte reduziert werden kann. Diese Funktion sollte man nur bei wirklich großen LOVs von einigen 10 000 Zeilen ver-wenden. Der Anwender kann diese Aufforderung jedoch durch die Eingabe eines %-Zeichens sehr einfach umgehen. Die bessere Lösung ist aber die Programmierung eines Key-Listval-Triggers, mit der ein derartiger Aufruf definitiv verhindert werden kann. Ein ganz anderer Ansatz besteht darin, auf Forms-Ebene die Property Interaction Mode auf Non-Blocking zu setzen, so dass eine versehentlich gestartete, lang laufende LOV abgebro-chen werden kann. Automatic Display = Yes führt dazu, dass die LOV immer geöffnet wird, wenn das Item den Fokus erhält. Eine wenig sinnvolle Operation. Automatic Refresh = Yes bewirkt, dass bei jedem Aufruf der LOV (auch beim Einsatz für die Validierung) die zugehörige Record Group neu aufgebaut wird. Das kann bei großen LOVs unter Umständen eine zeitaufwendige Aktion darstellen. Automatic Select =Yes schließt die LOV und gibt (einen oder alle) Werte zurück, wenn die LOV auf eine Zeile reduziert wurde. Sehr zu empfehlen. Automatic Skip = Yes macht einen automatisches Next-Field bei der Rückkehr aus der LOV. Sollte gesetzt sein. Automatic Position und Automatic Column Width sollten auf ihren Defaultwerten Yes gesetzt bleiben. Width und Height müssen entsprechend der Breite und Höhe der LOV gesetzt werden, die X Position und Y Position belassen wir auf den Defaultwerten 0, da wir Forms ja ohnehin angewiesen haben, die LOV automatisch zu positionieren. Das Fenster LOV Column Mapping ist der Ort, an dem wir die Spaltenbreite und die Spal-tenüberschrift bestimmen. Außerdem legen wir hier fest, welche Daten der LOV in welche Formularfelder zurückgeschrieben werden sollen (die Spalte EMPNO ist hier dem Formu-larfeld EMP.MGR zugeordnet und – was nicht sichtbar ist – die Spalte ENAME dem Formularfeld EMP.MGR_NAME).

4.3 Der lesende Zugriff

95

Spalten, die nicht angezeigt, aber trotzdem zurück geschrieben werden sollen, bekommen eine Display Width von 0 zugewiesen. Ein bisschen Spielen ist immer angesagt, um die optimale Breite des LOV-Fensters und von deren Spalten zu ermitteln.

4.3.2.3 Datenkopplung

Wie ist eine LOV datenmäßig mit dem rufenden Formular gekoppelt? Der Inhalt des Items, dem die LOV zugeordnet ist, kann an die LOV übergeben werden und dient dort a) der Autoreduction und b) der Validierung (siehe unten). Alle von der Record Group gelieferten und von der LOV vielleicht angezeigten Felder können im Formular an die Items zurückgegeben werden.

Autoreduction Eine interessante Eigenschaft der LOV ist die so genannte Autoreduction. Dabei wird die Menge der angezeigten Zeilen aufgrund der Eingabe durch die Anwender immer stärker eingeschränkt. In diese Funktionalität wird nur die erste dargestellte Spalte einbezogen. Autoreduction funktioniert nur, solange der Fokus im Datenbereich der LOV und nicht im Find-Feld liegt. Eine Sortierung der Sätze nach dem ersten angezeigten Begriff ist hilf-reich, aber nicht erforderlich. Auch wenn es bei Autoreduction so aussieht, als ob eine wiederholte Query mit der zusätzlichen Einschränkung der Where-Klausel auf "… 1. Feld like bisherige Eingabe || % …" durchgeführt wird, findet hier keine Kommunikation mit der Datenbank statt.

Validierung Eine auf Item-Ebene gesetzte Property ist Validate from List. Mit ihr können Sie festlegen, dass Forms den eingegebenen Wert gegen die möglichen Werte des 1. Feldes in der LOV prüft. Wenn der Wert existiert und eindeutig ist, werden alle zugeordneten Felder der LOV an das Formular zurückgegeben. Wenn der Wert nicht existiert oder mehrmals vorkommt, wird die LOV automatisch geöffnet. Wenn der eingegebene Wert der Anfang eines der Werte im 1. Feld der LOV ist, wird die LOV bereits auf diesen Wert eingeschränkt. Diese Validierungsoperation erfolgt ohne Zugriff auf die Datenbank ausschließlich durch eine Prüfung gegen den Inhalt der Wertemenge der Record Group.

4.3.3 List-Items

List-Items haben zwei Funktionen: Erstens wird bei der Anzeige der Inhalt des Feldes (häufig ein Fremdschlüssel) in einen anderen (häufig ein erläuternder Text zu einem Schlüssel) umgesetzt, und zweitens wird die Menge der zulässigen Eingabewerte auf die Menge der übersetzbaren Werte eingeschränkt. Nicht die Abteilungsnummer, sondern der Abteilungsname, nicht der Datenbank-interne Code "le" des Familienstandes, sondern des-sen allgemein verständlicher Klartext "ledig" wird dargestellt und eingegeben.

4 Daten

96

Der Entwickler legt fest, wie List-Items dargestellt werden sollen: als T-List (alle Alterna-tiven sind immer sichtbar), als Poplist (der zulässige Wertevorrat wird erst bei Klick auf ein Anforderungs-Icon dargestellt) oder als Combobox (verhält sich wie eine Poplist, erlaubt aber die freie Eingabe auch nicht dargestellter Werte). Für die Übersetzung der Anzeige und die Einschränkung der Eingabewerte verwendet das List-Item eine (interne) Tabelle mit Anzeige-/Wertpaaren. Es gibt zwei Möglichkeiten, dem List-Item diese Tabelle bekannt zu geben: Statisch (eher selten genutzt)

Der Entwickler legt eine fixe Menge von Anzeige-/Wertepaaren an. Diese Methode scheint immer dort angezeigt, wo sich diese Menge über die Lebensdauer des Systems als konstant darstellt (der oben genannte Familienstand dürfte in diese Kategorie fal-len). Dynamisch

Die Menge von Anzeige-/Wertepaaren wird dem List-Item zur Laufzeit des Formulars zugewiesen. Forms Builder bietet hierfür Built-In-Funktionen an: POPULATE_LIST ersetzt den Inhalt einer Liste mit dem Inhalt einer Record Group. ADD_LIST_ITEM erweitert die Menge der Anzeige-/Wertepaare um einen weiteren

Eintrag. DELETE_LIST_ITEM löscht ein vorhandenes Anzeige-/Wertepaar. CLEAR_LIST löscht den Inhalt einer Liste.

Forms beantwortet (im Prinzip) den Aufruf dieser Funktionen mit einer Fehlermeldung, wenn der Inhalt der Properties Initial Value oder Mapping of other Values nicht mehr in der geänderten Zuordnung auftaucht oder wenn der Block, zu dem das betroffene List-Item gehört, bereits gefüllt ist. Der häufigste Fall wird daher der Einsatz von POPULATE_LIST im Pre-Form Trigger sein.

Ein Beispiel für den dynamischen Fall: Im Block "EMP" soll das List-Item "DEPTNO" mit dem Inhalt der Tabelle "DEPT" gefüllt werden. Diese Funktionalität wird aus einem Pre-Form-Trigger heraus aufgerufen. (Da-raus resultiert dann auch, dass eine Änderung der Tabelle "DEPT" bis zum nächsten Start in unserem Formular keine Auswirkung auf den Inhalt des List-Items hat). Nicht betrachtet wird in diesem Beispiel die Behandlung eines Initialwertes für unser Item "DEPTNO".

Listing 4.1 Der Pre-Form Trigger

fillTheList('EMP.DEPTNO, 'select dname ' ||-- angezeigter Wert ' ,to_char(deptno)' ||-- Inhalt des Feldes, -- muss vom Typ character sein 'from dept' || 'order by dname'); -- Sortierung

4.3 Der lesende Zugriff

97

Listing 4.2 Die Program Unit fillTheList

procedure fillTheList(pItem in varchar2 -- das List Item ,pSelect in varchar2 -- das Select Statement mit dem -- Inhalt für das List Item ) is -- record group deren Daten an das List Item zugewiesen werden rgID recordgroup; -- Variable um das Eregbnis des populate_grup nTmp number; begin -- erzeuge eine record group rgID := create_group_from_query('DUMMY', pSelect); -- fülle die record group mit dem angegebenen SQL Statemant nTmp := populate_group(rgID); -- behandle einen Fehler if nTmp>0 then Fehlerbehandlung end if; -- ersetze den Inhalt des List Items mit dem Inhalt der record group Populate_list(pItem, rgID); -- lösche die record group delete_group(rgID); end;

Ein zweites Beispiel für einen dynamischen Fall: Das List-Item "WOCHENTAG" soll die Namen der Wochentage anzeigen. Die entspre-chende Spalte in der Datenbank enthält den klein geschriebenen Wochentagsnamen. Diese Liste soll beginnend mit dem morgigen Tag sieben Namen enthalten. Sie soll kalender-gerecht sortiert sein: am Dienstag soll die Liste die Werte Mittwoch, Donnerstag, …, Dienstag in dieser Reihenfolge anzeigen. Das List-Item soll ein obligatorisches Feld sein. Das folgende Codestück im Pre-Form Trigger macht's:

declare wochentag varchar2(16); begin -- Leere die Liste clear_list('wochentag'); for i in 1..7 loop -- ermittle den nächsten Wochentagsname in deutscher Sprache wochentag:=to_char(sysdate+i,'Day','nls_date_language=''GERMAN''') -- füge den Wochentagsname an der richtigen Position ein add_list_element('wochentag', i, wochentag,rtrim(lower(wochentag))); end loop; -- schalte die Property "Required" ein set_item_property('wochentag',required,property_true); end;

Hinweise: Existiert ein Wert aus der Datenbankspalte nicht in der Umsetztabelle, dann zeigt

Forms diesen Datensatz (ohne einen Hinweis zu geben!) nicht an. Forms ergänzt die Tabelle um einen Leersatz, falls das List-Item als optional definiert

wurde (Property Required sitzt auf No). Im Query-Fall bietet Forms immer den leeren Wert an, um damit das List-Item von der

Where-Klausel der Query auszuschließen. Forms prüft nicht auf Eindeutigkeit der Daten in der Umsetzungstabelle. Ein bisschen ( ) Mehraufwand erfordert das Setzen eines Defaultwerts.

4 Daten

98

4.3.4 Globals

Globale Variablen sind (nicht nur in Forms) ein sehr altes Konzept, um Werte über die Laufzeit eines Formulars hinaus aufzubewahren und/oder Daten zwischen Formularen zu transportieren. Forms Builder kennt für diese Aufgaben mehrere Verfahren: Forms Globals

Gibt es immer schon; sie werden von der Forms Runtime als Speicherplatz vom Typ Character der maximalen Länge 4000 verwaltet. Package Globals

Variablen, die in der Spezifikation eines Packages definiert werden. Parameter

Parameter sind Forms-Objekte. Für die Übergabe an andere Formulare oder Reports können Parameter in einen Container, die Parameterliste, verpackt werden. Parameter-listen sind Forms-Objekte, die im Gegensatz zu den meisten anderen Forms-Objekten nur zur Laufzeit per PL/SQL angelegt werden können.

Tabelle 4.1 zeigt einige Eigenschaften der drei Globaltypen. Wie man sehen kann, gibt es keine Ideallösung.

Tabelle 4.1 Eigenschaften der verschiedenen Parametertypen

Eigenschaft Forms Globals Package Globals Parameter

Lebensdauer:– Beginn

mit der ersten Zuweisung mit dem ersten Zugriff auf das Package

mit dem Start der Formulare

– Ende Ende der Forms Runtime (= Exit der letzten Formular) oder mit Aufruf des Built-Iin ERASE

Ende der Session Exit der Formulare

Sichtbarkeit für alle Formulare einer Forms Runtime

alle Formulare derselben Session

alle Formulare im selben Aufrufspfad

typisiert nein (nur Typ character) ja, voller PL/SQL Umfang

ja, aber nur Character, Number oder Date

Defaultwerte nein (progammiert ja) ja ja

Zugriff lesend/schreibend lesend/schreibend lesend/schreibend; eine Änderung wird nicht an die aufrufende Formular zurückgegeben.

Zugriffs-methode

direkt per global.xxx oder indirekt über NAME_IN, COPY oder DE-FAULT_VALUE

direkt per package.xxx oder via Getter-/Setter Routinen

direkt per parameter.xxx

Forms Globals werden im Adressraum einer Instanz der Forms Runtime realisiert. Sie sind deshalb für alle Formulare sichtbar, die innerhalb ein und derselben Runtime laufen. Die fehlende Typisierbarkeit – alle Globals sind vom Typ Character – ist bisweilen lästig.

4.3 Der lesende Zugriff

99

Das Ablegen von Zahlen mit Nachkommastellen scheint noch beherrschbar, beim Ablegen von Inhalten vom Typ Date oder Timestamp haben wir nur dann eine Chance, wenn wir grundsätzlich mit expliziter Typwandlung (TO_CHAR(..) und TO_DATE(..)) und dem immer gleichen Formatstring arbeiten. Der Versuch, auf eine Global, die noch nicht angelegt wurde, lesend zuzugreifen, führt zu einem Laufzeitfehler. "FRM-40815: Variable … does not exist". Um diese offensichtliche Demonstration der eigenen fehlerbehafteten Arbeitsweise zu vermeiden, empfiehlt sich beim Zugriff auf eine Global, das folgende Codestück einzusetzen. Hier im Beispiel die Global "autoquery" in einem When-New-Form-Instance Trigger:

-- wenn die global existiert, dann tu nix -- wenn sie nicht existiert, dann lege sie an und -- weise ihr den Wert yes zu default_value('no','global.autoquery'); -- ab jetzt kann gefahrlos auf global.autoquery zuggriffen werden if :global.autoquery='yes' then execute_query; end if;

PL/SQL-Programme können auf eine globale Variable XXX mit der Notation ":global.XXX" lesend und schreibend zugreifen. Es können auch die Built-Ins für indirekte Adressierung NAME_IN ('global.xxx') zum Lesen, COPY (…wert…,'global.xxx') zum Schreiben oder DEFAULT_VALUE (…wert…,'global.xxx') zum bedingten Schreiben eingesetzt werden. Im Gegensatz zu Forms Globals stellen die Package Globals das modernere und elegante-re Konzept dar. Sie haben keinen der Nachteile der Forms Globals; dafür andere: sie sind auf eine Session begrenzt. Wenn wir in einer Applikation mehrere Formulare gleichzeitig offen halten müssen und diese Formulare zum Beispiel aus Gründen der Concurrency in separaten Sessions laufen müssen, dann helfen uns Package Globals nichts, wenn diese Formulare Daten austauschen sollen. Die Entscheidung, ob auf diese Variablen direkt zugegriffen werden darf oder ob dafür Getter- und Setter-Routinen zu verwenden sind, sollte pragmatisch gefällt werden. Häufig hat man die Chance, einen lokalen Software-Engineering-Guru zu befragen oder auf beste-hende Programmierrichtlinien zurückgreifen zu können. Parameter sind das jüngste Konstrukt. Sie sind Forms-Objekte und sie sind typisiert (im-merhin haben wir CHARACTER, DATE und NUMBER; war da nicht noch was ande-res?). Sie können mit Initialwerten belegt werden. Sie sind nicht von der Session abhängig. Sie stehen allen im Aufruf-Stack befindlichen Formularen zur Verfügung. Sie werden angelegt mit dem Start ihrer Formulare und verschwinden wieder, wenn ihr Formular ge-schlossen wird. Ihr Nachteil besteht darin, dass keine Daten aus dem gerufenen Formular an das rufende zurückgegeben werden können (ein Konzept, das wir auch von Umgebungsvariablen in Betriebssystemen kennen). Ein (Nutzen stiftendes) Alleinstellungsmerkmal ist auch, dass sie in der Property Initial Value eines Items eingetragen werden können. Das funktioniert allerdings nur, wenn sie

4 Daten

100

mit dem Präfix :PARAMETER. versehen werden (Achtung: Der Text "PARAMETER" unter den folgenden Parameternamen muss zwingend in Großbuchstaben geschrieben wer-den).

4.4 Der ändernde Zugriff

In einem per Wizard erzeugten Formular ändert Forms immer in der gleichen Tabelle, aus der es auch die Daten liest. Manuell können aber drei weitere Arten gewählt werden: 1. Forms ändert in einer anderen Tabelle/View. In der Praxis kaum zu finden. 2. Forms verwendet Prozeduren, um die Daten in die Datenbank zu schreiben. Schreit

wegen der Komplexität der erforderlichen Prozeduren nach einem Generator. 3. Forms verwendet die so genannten Transaction Triggers: On-Insert, On-Update, On-

Delete und On-Lock. Diese Trigger sind gelegentlich zu sehen. Der Einsatz von Views mit Instead-of-Triggern ist hier aber das eindeutig bessere Konzept.

Diese Basisfunktionalität kann um einige Trigger, die Forms im Commit-Fall feuert, er-weitert werden. 1. Pre-Commit

nur auf Forms-Ebene definierbar 2. Pre-Insert/Pre-Update/Pre-Delete 3. Post-Insert/Post-Update/Post-Delete 4. Post-Forms-Commit

Nur auf Forms-Ebene definierbar. Feuert, nachdem alle DML-Operationen gegen die Datenbank gelaufen sind, aber bevor das abschließende Commit erfolgt; das heißt, die Änderungen können noch zurückgerollt werden.

5. Post-Database-Commit Nur auf Forms-Ebene definierbar. Feuert sobald alle DML Operationen ausgeführt und committed wurden; d.h., es ist kein Rollback mehr möglich.

Die Trigger der Ebenen 1 bis 4 können beliebige DML-Operationen gegen die Datenbank ausführen, die im Fehlerfall ordentlich zurückgerollt werden. Auf den Ebenen 1 und 2 ist häufig PL/SQL Code zu finden, der die Eingaben der Anwender prüft und gegebenenfalls durch Zuweisung an einzelne Felder des Formulars ergänzt. Wenn der Anwender ein Formular speichert, führt Forms (unter anderem) die genannten Trigger in folgender Reihenfolge aus:

starte den Pre-Commit Trigger wenn es neue, geänderte oder zu löschende Sätze gibt dann starte alle Pre-(Insert/Update/Delete) Trigger starte alle On-(Insert/Update/Delete) Trigger starte alle Post-(Insert/Update/Delete) Trigger

4.5 Interna

101

starte den Post-Forms-Commit Trigger starte den Post-Database-Commit Trigger

DML-Operationen in Triggern Es ist möglich, in Triggern außerhalb der oben genannten Ebenen eine in der Datenbank abgelegte Prozedur aufzurufen, die DML-Operationen auf irgendwelchen Tabellen aus-führt. Davon ist dringend abzuraten! Änderungen in der Datenbank dürfen nur durch Än-derungen des Inhalts von Forms-Items oder in einem der Commit Trigger vorgenommen werden. Besteht trotzdem die Notwendigkeit, eine solche Prozedur aufzurufen, empfiehlt sich ein Umweg, den man wie folgt beschreiben kann: Im Trigger, der die ändernde Funktion aufruft, setze ein Flag (zum Beispiel in einem Control Block), markiere den Status des aktuellen Records als "CHANGED" oder "INSERT" mittels SET_RECORD_PROPERTY und rufe das Built-In COMMIT_FORM. Im Pre-Insert oder Pre-Update Trigger, wenn das Flag gesetzt ist, setze das Flag zurück und rufe die ändernde Prozedur auf. Eine Ausnahme von der Regel „keine DML-Operation in Nicht-Transaktionstriggern“ ist nur in einem Fall zulässig: wenn die gerufene Prozedur eine autonome Transaktion realisiert ("pragma autonomous transaction" plus "commit;" oder "rollback;" innerhalb des PL/SQL Codes).

4.5 Interna

In diesem Abschnitt wollen wir einige der Entwurfsentscheidungen des Forms Entwickler-teams betrachten und überlegen, wie wir daraus Nutzen ziehen können.

4.5.1 Cursor

Forms verwendet für den Zugriff auf eine Tabelle oder View in der Datenbank Cursor (was wenig überrascht). Im Normalfall werden fünf Cursor standardmäßig erzeugt, je einen für die Standard-Zugriffsoperationen:

Tabelle 4.2 Standardcursor

Zweck SQL Code

select select rowid, … SpaltenSel … from Tabelle1 where … order by …

insert insert into Tabelle2( … SpaltenUpd …) values(…)

delete delete from Tabelle2 where rowid=…

update update Tabelle2 set SpaltenUpd = :i, … where rowid=…

lock select … SpaltenSel … from Tabelle2 where rowid= ... order by … for update nowait

4 Daten

102

"SpaltenSel" steht für alle Datenbank-Items des Blocks; genauer: für den Inhalt der Proper-ty Column Name – oder, falls diese Property leer ist: den Item-Namen – all jener Items, deren Property Database Item auf Yes gesetzt ist. "SpaltenUpd" steht für alle Datenbank-Items des Blocks; genauer: für den Inhalt der Pro-perty Column Name – oder, falls diese Property leer ist, den Itemnamen – all jener Items, deren Property Database Item auf Yes und deren Property Query Only auf No gesetzt sind. "Tabelle1" steht für den Inhalt der Property Query Data Source Name. "Tabelle2" steht für den Inhalt der Property DML Target Name oder, falls diese Property leer ist, den Inhalt der Property Query Data Source Name.

4.5.2 Verwendung der ROWID

Wir sehen, dass Forms extensiv von der ROWID Gebrauch macht. Das ist klar, da die ROWID die einfachste und schnellste Methode ist, einen Datensatz eindeutig zu identifi-zieren. Dummerweise versagt diese Methode aber bei View-basierten Data Blocks: eine View kennt keine ROWID. Glücklicherweise gibt es aber eine Möglichkeit, Forms anzu-weisen, statt der ROWID den Primary Key zu verwenden. Dazu müssen einige Properties auf andere als ihre Standardwerte gesetzt werden. Abbildung 4.14 und die folgende Tabel-le 4.3 zeigen, welche Properties wie und wo gesetzt werden müssen, um auf die ROWID verzichten zu können.

Abbildung 4.14 Properties für ein Arbeiten ohne ROWID

4.5 Interna

103

Tabelle 4.3 Properties für ein Arbeiten ohne ROWID

Objekt, Bereich Property Wert Wirkung

Data Block, Database Key Mode Updateable oder Non-Updateable

Verwende statt der Rowid die Primary Key Items.

Item, Database Primary Key Yes Definiert ein oder mehrere Items als Primary Key.

4.5.3 Default Values in der Datenbank beim Insert

Das für ein Insert in eine Tabelle der Datenbank generierte Statement enthält in der Spalten-liste alle Datenbank-Items eines Data Blocks (siehe Tabelle 4.2). Wenn der Anwender ein Item nicht gefüllt hat, dann wird bei dieser Vorgehensweise ein NULL in die zugeordnete Spalte geschrieben. Damit wird aber die Zuweisung eines Defaultwertes auf Datenbank-ebene verhindert. Es gibt mehrere Lösungen (wie immer) mit unterschiedlichen Nachteilen (wie immer), um dieses Verhalten zu korrigieren. Das Item wird in dem Formular gar nicht aufgeführt. Nachteil: Es kann nicht für die

Suche verwendet werden und ist natürlich auch nicht veränderbar. Das Item wird in dem Formular zwar aufgeführt, seine Property Query Only wird auf

Yes gesetzt. Nachteil: Es ist nicht veränderbar. Eine wirklich überzeugende Lösung, die auf den Einsatz von PL/SQL verzichtet, gibt es nicht.

4.5.4 Updates

Im Normalfall enthält das generierte Update Statement eine Zuweisung für alle Datenbank-Items eines Data Blocks, unabhängig davon, ob der Inhalt vom Anwender geändert wurde oder nicht. Das ist dann misslich, wenn für eine Tabelle das Updaterecht auf bestimmte Spalten eingeschränkt ist. Das „überflüssige“ Update auf eine gesperrte Spalte führt dann nämlich zu einem kaum erklärbaren Fehler. Dieses Verhalten lässt sich ändern, indem die Property Update Changed Columns Only auf Yes gesetzt wird. Dann werden nämlich pro Update nur die Spalten aufgenommen, deren Inhalt sich tatsächlich geändert hat. Eine weitere Besonderheit: Beim Einstellen der Property Key Mode kann zwischen Up-dateable und Non-Updateable gewählt werden. Im Fall Updateable werden die als Prima-ry Key definierten Spalten in der Set-Liste des Update Statements aufgeführt, das heißt, die Primary-Key-Spalten werden geändert (welche Konsequenzen ein änderbarer Primär-schlüssel in der Datenbank hat, ist natürlich eine andere spannende Frage). Im Fall Non-Updateable enthält die Set-Liste keine als Primary Key definierten Spalten. (Freundli-cherweise sperrt sich Forms in diesem Fall auch gegen eine Änderung dieser Items durch den Endanwender.)

4 Daten

104

Tabelle 4.4 zeigt die Wirkung der Properties Key Mode und Update Changed Columns On-ly auf das von Forms generierte Update-Statement für folgendes Beispiel: Ein Formular enthält einen Block. Der Block basiert auf der Tabelle X und enthält die Items f1,f2,f3. Item f1 ist als primary key Item markiert. Der Anwender ändert den Inhalt von Item f1.

Tabelle 4.4 Wirkung von Key Mode und Update Changed Columns Only

Key Mode Update Changed Columns Only

Das generierte Update Statement

Automatic No Update X set f1=:1, f2=:2, f3=:3 where rowid=:4

Automatic Yes Update X set f3=:1 where rowid=:2

Updateable No Update X set f1=:1, f2=:2, f3=:3 where f1=:4

Updateable Yes Update X set f3=:1 where f1=:2

Non-Updateble No Update X set f2=:1, f3=:2 where f1=:3

Non-Updateble Yes Update X set f3=:1 where f1=:2

4.5.5 Returning Values

Forms kann angewiesen werden, nach einem Insert oder Update Daten aus der Datenbank zurückzulesen. Wann ist das sinnvoll? Nehmen wir an, ein Formular basiert auf der Tabel-le "S_EMP". Der eindeutige Schlüssel "ID" – die Personalnummer – wird in dem Formular angezeigt, ist nicht eingebbar und nicht änderbar, kann aber abgefragt werden. Die Spalte "ID" wird gefüllt per Datenbank-Trigger (on-insert for each row). Mit der Block Property DML Returning Value unter Advanced Database kann festgelegt werden, dass alle durch serverseitige Logik geänderten Werte in die korrespondierenden Formularfelder zurückgelesen werden. Wird diese Property auf NO gesetzt, erhält der Anwender beim nächsten Commit auf den gleichen Datensatz den Fehler "FRM-40654: Record has been updated by another user. Re-query to see change". Ein großes Manko stellt die Einschränkung in der Datenbank dar (auch noch unter 10g), dass Views mit Instead of Triggern die Returning Values nicht nutzen können.

4.5.6 Namensauflösung

Die oben genannten SQL Statements für das Select, Insert, Update, Delete und Lock ent-halten einen Tabellennamen. In welchem Schema sucht die Datenbank nach dieser Tabel-le? Die Datenbank definiert folgende Hierarchie bei der Suche nach einem Object X: 1. Im Schema des aktuellen Users. Dort dürfte die Tabelle vermutlich nie zu finden sein. 2. Suche ein lokales Synonym im Schema des aktuellen Users mit dem Namen X. Das

heißt, wir brauchen pro User, der sich in die Applikation einloggt, einen Satz an priva-ten Synonymen für alle Tabellen der Applikation – ein Alptraum: beim Anlegen eines neuen Users oder beim Erweitern des Tabellensatzes muss hier angepasst werden.

4.5 Interna

105

3. Suche ein globales Synonym mit dem Namen X. Hier kann man darauf warten, dass zwei Applikationen ein und denselben Synonymnamen für zwei verschiedene Objekte verwenden.

4. Der Tabellennamen kann mit einem Schemanamen (zweckmäßigerweise dem des Eigentümers der Applikation) qualifiziert werden (in der Form "Schema.Tabelle"). Die Namensauflösung der Datenbank startet dann in dem angegebenen Schema. Damit wären wir eigentlich fertig. Allerdings lehrt die Erfahrung, dass wir diese Qualifizie-rung in mindestens einem Formular bei mindestens einem Objekt vergessen und der Anwender sich bitterlich über die miese Qualität der Programme beklagt.

5. Die finale Abhilfe sind wohl Instruktionen in einem Pre-Form Trigger wie in Listing 4.3. Damit weisen wir die Datenbank an, alle nicht qualifizierten Objekte mit dem angegebenen Schemanamen zu qualifizieren. Es ergibt natürlich keinen Sinn, den Namen des Applikations-Eigentümers als Textkonstante im Triggertext zu hinterlegen. Sinnvoll ist, ihn aus der Umgebung, zum Beispiel über einen Parameter, abzuholen.

Listing 4.3 Pre-Form Trigger

DECLARE l_user varchar2(30) := 'MYAPP'; BEGIN forms_ddl('alter session set curent_schema=' || l_user); END;

4.5.7 Verwendung von Views

Im Folgenden wollen wir uns die Vorteile des Einsatzes von Views statt Tabellen als Mit-tel des Zugangs zur Datenbank ansehen. Für die beschriebene Aufgabenstellung soll daher eine View-basierte Lösung beschrieben werden. Ein Formular soll eine Liste aller Mitarbeiter mit der Personalnummer, dem Nach-

namen des Mitarbeiters, dessen Eintrittsdatum sowie dem Namen der zugeordneten Abteilung erhalten. Außerdem wollen wir gern das Fixgehalt des Mitarbeiters, die Gehaltssumme seiner Abteilung und den Anteil des Mitarbeitergehalts bezogen auf die Gehaltssumme sehen. Dieser Wert soll in ganzen Prozent und gerundet auf den nächs-ten durch 5 teilbaren Wert angezeigt werden. Abteilungen ohne Mitarbeiter sollen nicht angezeigt werden. Die Anzeige (siehe Listing 4.4) soll nach Abteilungsnamen aufsteigend, Gehaltsanteil

absteigend, dann nach Mitarbeiternamen und schließlich nach Eintrittsdatum sortiert sein. Keiner Abteilung zugeordnete Mitarbeiter sollen zuerst angezeigt werden. Mit-arbeiter ohne Fixgehalt, ihr Gehaltsanteil ist leer, sollen innerhalb der Abteilung als Erste aufgeführt werden. Alle Felder sollen abfragbar sein. Die Felder Personalnummer und Nachname sind Pflichtfelder. Die Personalnummer

wird automatisch mit einer Sequence (siehe Listing 4.5 und Listing 4.6) initialisiert, der Nummernkreislauf darf Lücken enthalten.

4 Daten

106

Die Personalnummer ist eindeutig, kann aber geändert werden. Das Eintrittsdatum soll mit dem 1. des kommenden Monats initialisiert werden. Abteilungsnummern sollen nicht angezeigt werden.

Listing 4.4 Die View

create or replace view emp_v(PersNr, Nachname, Eintritt, Abteilung ,Gehalt, GehSsumme, GehAnteil) as select empno, ename, hiredate, deptno, sal, from (select e.empno, e.ename, e.hiredate, e.deptno, d.dname, e.sal, sum(sal) over(partition by nvl(e.deptno,-1)) salsum, round(100*ratio_to_report(e.sal) over(partition by nvl(e.deptno,-1))/5 )*5 as salrat from dept d, emp e where d.deptno(+)=e.deptno) order by dname desc nulls first, salrat desc nulls first ,ename, hiredate;

Listing 4.5 Die Sequence für die Personalnummern

create sequence emp_empno_seq start with 8100 maxvalue 9999 nocache nocycle;

Listing 4.6 Instead of Trigger

create or replace trigger emp_v_instead instead of insert or update or delete on emp_v begin if inserting then insert into emp(empno, ename, hiredate, deptno, sal) values(:new.PersNr, :new.Nachname, :new.Eintritt, :new.Abteilung, :new.Gehalt); elsif deleting then delete from emp where empno=:old.PersNr; elsif updating then update emp set empno=:new.PersNr ,ename=:new.Nachname ,hiredate=:new.Eintritt ,deptno=:new.Abteilung ,sal=:new.Gehalt where empno=:old.PersNr; end if; end;

Unter Verwendung des Data Block Wizards und des Layout Wizards erstellen wir eine ers-te Version unseres Formulars, basierend auf der "EMP_V"-View. Danach ist noch etwas Nacharbeit notwendig. Schlussendlich sehen wir das in Abbildung 4.15 gezeigte Formular. Wir haben bereits drei neue Mitarbeiter erfasst: zwei mit der Nummer 8121 und 8120, deren Abteilung noch nicht bekannt ist, und einen mit der Nummer 8000, deren Gehalt noch nicht fixiert scheint. Der geneigte Leser möge nachvollziehen, dass alle geforderten Spalten angezeigt werden und dass die Sortierung offensichtlich stimmt. Das Ergebnis der Arbeit der Wizards werden wir mit den im Folgenden beschriebenen manuellen Nacharbeiten noch ein bisschen verbessern. Abbildung 4.16 zeigt die Einstel-lungen für die beiden Visual Attributes VA_NORMAL und VA_CURRENT. VA_NORMAL wird benutzt, um die grafischen Eigenschaften der Items festzulegen. Mit VA_CURRENT legen wir fest, in welcher Farbe der aktuelle Datensatz dargestellt werden soll.

4.5 Interna

107

Abbildung 4.15 Das Ergebnis in seiner vollen Pracht

Abbildung 4.16 Visual Attributes

Für die Ausgabe von Meldungen werden wir die folgende (Abbildung 4.17) Alertbox ver-wenden.

4 Daten

108

Abbildung 4.17 Alertbox

Dann ändern wir noch einige Properties:

Tabelle 4.5 Geänderte Properties

Typ Name Property Wert Wirkung

Block EMP_V Key Mode Updateable Verwende keine Rowid (siehe oben).

Current Record Visual …

VA_CURRENT Zeige die aktuelle Zeile mit einer anderen Hintergrundfarbe.

Item PERSNR Primary Key Yes Verwende als Schlüssel statt der Rowid das Item PERSNR.

Initial Value :SEQUENCE. emp_seq. nextval

Hole den Initialwert aus der Sequence.1

Required Yes Muss-Feld

Item NACHNAME Required Yes Muss-Feld

Case Insensitive Query

Yes Groß-/Kleinschreibung spielt im Queryfall keine Rolle.

Item EINTRITT Required Yes Muss-Feld

Format Mask dd.mm.yyyy

Item GEHALT Format Mask 999G990D00

Item GEHSUMME, GEHANTEIL

Query Only Yes Die Spalten fehlen beim von Forms generierten Insert und Update Statement.

1 Die Zeichenfolge :SEQUENCE muss in Großbuchstaben geschrieben sein.

4.5 Interna

109

Typ Name Property Wert Wirkung

Keyboard Navigable

No Die beiden Items sind nur im Queryfall per Tastatur erreichbar.

Insert Allowed, Update Allowed

No Sperre gegen Erfassung und Änderung

Format Mask 999G990D00

Item PERSNR bis ABTEILUNG

Prompt Alignment

Start Prompting-Text wird am linken Feldrand ausgerichtet.

Item GEHALT, GEHSUMME, GEHANTEIL

Justification End Feldinhalt rechtsbündig ausrichten

Prompt Alignment

End Prompting-Text wird am rechten Feldrand ausgerichtet.

Item Alle Visual Attribute Group

VA_NORMAL Einheitliches Outfit der Items

Dann gibt es noch einige Trigger: Ein Pre-Form Trigger auf Forms-Ebene , der das List-Item ABTEILUNG befüllt:

declare rgID recordgroup; nTmp number; begin rgID := create_group_from_query('DUMMY', 'select dname, to_char(deptno) from dept order by 1'); nTmp := populate_group(rgID); Populate_list('EMP_V.ABTEILUNG', rgID); delete_group(rgID); end;

Ebenfalls auf Forms-Ebene: When-New-Form-Instance Trigger für das initiale Füllen der Maske

execute_query;

Ein When-Window-Closed für das Schließen der Formulare:

begin /* fülle das window closed icon mit Funktion */ do_key('exit_form'); end;

Auf Blockebene setzt ein When-Create-Record Trigger den Initialwert im Item EIN-TRITT:

begin if :emp_v.eintritt is null then :emp_v.eintritt:=last_day(sysdate)+1; end if; end;

Und dann brauchen wir auf Blockebene einen On-Lock Trigger, der das Standardlocking von Forms nachbildet. Dieser Trigger ist erforderlich, da das von Forms erzeugte SQL

4 Daten

110

Statement für das Sperren des aktuellen Datensatzes von der Datenbank mit Fehler "ORA-02014: cannot select FOR UPDATE from view with DISTINCT, GROUP BY, etc." abgewiesen wird.

declare eLck exception; pragma exception_init(eLck, -54); cursor cuLck(cEmpno number) is select empno, ename, hiredate, deptno, sal from emp where empno=cEmpno for update nowait; rcLck cuLck%rowtype; procedure Melde(mesTxt in varchar2) is /* gibt eine Meldung aus (quick and dirty) */ dum number; begin set_alert_property('AlFehler' ,alert_message_text, mesTxt); Dum:=show_alert('AlFehler'); end; begin open cuLck(:emp_v.PersNr); fetch cuLck into rcLck; if (rcLck.empno=:emp_v.PersNr or nvl(rcLck.empno, :emp_v.PersNr) is null ) and (rcLck.ename=:emp_v.Nachname or nvl(rcLck.ename, :emp_v.Nachname( is null ) and (rcLck.hiredate=:emp_v.eintritt or nvl(rcLck.hiredate, :emp_v.eintritt) is null ) and (rcLck.deptno=:emp_v.abteilung or nvl(rcLck.deptno, :emp_v.abteilung) is null ) and (rcLck.sal=:emp_v.Gehalt or nvl(rcLck.sal, :emp_v.Gehalt) is null ) then null; else Melde( 'Daten wurden seit der letzten Abfrage geändert.' || 'Bitte wiederholen Sie die Abfrage'); end if; close cuLck; exception when eLck then Melde( 'aktueller Datensatz in Bearbeitung.' || 'Versuchen Sie es später nochmals'); end;

4.5.8 Abartigkeiten

Schauen wir uns nochmals an, wie Forms den Select-Cursor zusammenbaut. Wir erstellen ein Formular mit einem Block, der auf der Tabelle emp basiert. Angezeigt werden die Spalten EMPNO und ENAME. Wir erzeugen ein weiteres Item SUMSAL, Data Type = Number, Database Item = Yes und Column Name = sum(sal) over(partition by deptno) (liefert die Gehaltssumme der Abteilung des angezeigten Mitarbeiters).

4.5 Interna

111

Dieses Formular zeigt die Daten ordentlich an. Das erzeugte select-Statement lautet "select rowid, empno, ename, sum(sal) over(partition by deptno) from emp". Update zickt zunächst ein bisschen rum und beklagt sich mit einer Fehlermeldung "ORA-00927: missing equal sign" im Statement "update emp set empno=:1, enam=:2, sum(sal) over(partition by deptno)=:3 where rowid=:4". Diese Meldung ist ein wenig irreführend. Nicht das "missing equal sign" ist das Problem, sondern der Versuch, einem Ausdruck etwas zuzuweisen. Souverän, wie wir zu arbeiten pflegen, setzen wir die Property Query Only des Items "SUMSAL" auf No. Und ab sofort können unsere Anwender insert, update und delete wie gewohnt über dieses Formular abwickeln. Etwas schwieriger wird es, wenn wir erlauben wollen, dass für das Feld "SUMSAL" auch eine Query Condition eingegeben werden darf. Wir wollen zum Beispiel nur solche Ange-stellten sehen, die in einer Abteilung mit einer Gehaltssumme < 10000 arbeiten. Das hierzu von Forms erzeugte Select Statement ist "select rowid, empno, ename, sum(sal) over(partition by deptno) from emp where (sum(sal) over(partition by deptno) > :1)" und wird mit der Meldung "ORA-00934: group function is not allowed here" quittiert. Schlecht! Aufgeben ist nicht unsere Sache. Wir löschen den Inhalt der Property Column Name des Items "Sumsal" und geben in die Property Query Data Source Name unseres Blocks den folgenden Text ein "(select rowid, empno, ename, sum(sal) over(partition by deptno) sum-sal from emp)". Und siehe da – die Query mit einer Einschränkung der Gehaltssumme < 10000 funktioniert! Das erzeugte Select Statement ist "select rowid, empno, ename, sum-sal from (select rowid, empno, ename, sum(sal) over(partition by deptno) sumsal from emp) where (sumsal < :1)". Wir können sogar der Property ORDER BY Clause mit "sumsal desc nulls first" eine Sortierung zuweisen. Doch halt! Das Anlegen eines neuen Stammsatzes funktioniert nicht. Das Insert Statement "INSERT INTO (select rowid, empno, ename, sum(sal) over(partition by deptno) sumsal from emp) (empno,ename) VALUES (:1,:2)" wird mit dem Fehler "ORA-01732: data ma-nipulation operation not legal on this view" zurückgewiesen. Gut, können wir verstehen. Frisch ans Werk! Setze die Property DML Data Target Name in unserem Block auf emp. Damit weisen wir Forms an, insert, update, delete und lock gegen die Tabelle emp auszu-führen. Formular generieren, ausführen. Voilà! Dieser Weg, auch wenn er zu dem gewünschten Ergebnis führte, sollte nicht eingeschla-gen werden! Diese Lösung hätten wir mit einer View (siehe Abschnitt 4.5.7) billiger, wahrscheinlich auch schneller, auf alle Fälle aber wartungsfreundlicher haben können (wenngleich mit reduziertem Spaßfaktor und ohne zeigen zu können, was wir alles drauf-haben).

4 Daten

112

4.5.9 Concurrency

Zwei Anwender in der Personalabteilung bearbeiten gleichzeitig Personalstammdaten. Nehmen wir an, sie geben die turnusmäßigen Gehaltserhöhungen ein. Wie hilft uns Forms, zu vermeiden, dass sich die beiden ihre Änderungen gegenseitig überschreiben oder dass sie einem Mitarbeiter eine Erhöhung zweimal gewähren? Forms bietet folgende Hilfen: Gegen gleichzeitige Änderungen/Löschungen

Beide Anwender holen sich denselben Satz auf den Bildschirm. Anwender 1 ändert den Satz. Noch bevor er seine Änderungen gespeichert hat, führt Anwender 2 im selben Satz (ohne zu wissen, dass er bereits geändert wurde) seine Änderung durch. Forms versucht, diesen Konflikt zu lösen, indem ein zu ändernder oder zu löschender Satz in der Datenbank gesperrt wird (mit einem select … for update nowait). Gegen mehrmalige aufeinanderfolgende Änderungen

Beide Anwender holen sich denselben Satz auf den Bildschirm. Anwender 1 ändert den Satz und speichert ihn. Anwender 2 ändert ihn (ohne zu wissen, dass er bereits geändert wurde) und speichert ihn. Forms weist auf diesen Konflikt hin, indem der Datensatz ein weiteres Mal aus der Datenbank gelesen und sein Inhalt auf bereits vorgenommene Änderungen untersucht wird. Gegen gleichzeitiges Erfassen eines neuen Satzes

Es soll ein neuer Mitarbeiter aufgenommen werden. Wie verhindert man, dass er zweimal erfasst wird? Forms und/oder die Datenbank können diesen Fall nur blockie-ren, wenn der Anwender bei der Erfassung einen eindeutigen Schlüssel, zum Beispiel die neue Personalnummer, vergibt. Soll Forms eine geeignete Prüfung vornehmen, müssen die Items, die den eindeutigen Schlüssel ausmachen, auf Itemebene mittels der Property Primary Key (zu finden in der Rubrik Database) benannt werden. Außerdem ist auf Blockebene die Property Enforce Primary Key (in der Rubrik "Database") auf Yes zu setzen.

Auf Forms-Ebene existieren die beiden Properties Isolation Mode und Locking Mode, die eine erweiterte Kontrolle der Funktionen zur Concurrency-Auflösung erlauben. Die Tabel-le 4.6 beschreibt ihre Wirkung.

Tabelle 4.6 Wirkung der Properties Isolation/Locking Mode

Properties im Forms-Modul

Isolation mode Locking mode Ändern eines Feldes oder Löschen eines Datensatzes führt zu:

Speichern der Änderungen führt zu:

read committed automatic lock record nowait, prüfen –

read committed delayed – lock record, prüfen

serializable automatic lock record nowait check serializable

serailizable delayed – check serializable

4.5 Interna

113

Erklärungen zum Inhalt der Tabelle: lock record nowait

Sperre einen Datensatz. Wenn nicht möglich, frage der Anwender mit "Could not reserve records (.. tries). Keep trying?", was weiter geschehen soll. lock record

Sperre den Datensatz und warte (endlos) auf die erfolgreiche Sperre. "prüfen" steht für:

Prüfe die Daten in der Datenbank gegen die Daten im Formular nach dem letzten exe-cute query. Im Fall einer Abweichung melde "FRM-40654: Record has been updated by another user. Re-query to see change". check serializable

Prüfe, ob nach Beginn der aktuellen Transaktion durch Forms eine andere Transaktion die gleichen Daten geändert hat. Wenn ja, melde "FRM-40509: Oracle error: unable to update record", Dahinter steht die Fehlermeldung der Datenbank "ORA-08177: can't serialize access for this transaction". (Dieser Fehler kann nur durch Start einer neuen Transaktion behoben werden).