Principi OO in un`applicazione Oracle Developer

Transcript

Principi OO in un`applicazione Oracle Developer
È possibile utilizzare alcune funzionalità presenti
in Oracle Developer e Designer a partire dalla versione 2.0
per applicare alcuni principi di Object Orientation alle
applicazioni sviluppate con questi strumenti
di Massimo Ruocchio
Principi OO in un’applicazione
Oracle Developer
S
ebbene le architetture del
DBMS Oracle 7.x e di Oracle
Developer non si possano certo definire ‘Orientate agli Oggetti’, è possibile prendere in prestito
alcuni concetti dalla filosofia OO per
migliorare le prestazioni e la manutenibilità del codice prodotto con questi
strumenti. Quest’articolo è ispirato alla
seguente regola: nessun’istruzione SQL
deve essere inserita nel codice che viene eseguito sul client (cioè nei moduli
Oracle Developer).
Alla regola è ammessa la sola eccezione
delle List Of Values (LOV). L’applicazione di questa semplice regola, come
vedremo, consente di migliorare notevolmente sia le prestazioni sia la mantenibilità del codice.
In quest’articolo vedremo molti esempi di scrittura del codice, per realizzare le più comuni funzionalità di un
modulo Oracle Forms rispettando la
regola esposta.
Se si intende generare automaticamente il codice Developer utilizzando
Oracle Designer, bisogna avere cura
di rispettare alcuni principi nella fase
di disegno dei moduli; la questione
sarà affrontata meglio in seguito. Per
tutti gli esempi, saranno utilizzate due
tabelle: quella dei comuni, chiamata
COMUNI, che ha come chiave la colonna CODISTAT (codice ISTAT del
comune) e quella delle località, che ha
una foreign-key verso la tabella dei comuni sulla colonna LOC_CODISTAT.
Lo script di creazione delle due tabelle
è contenuto nel Listato 1.
Massimo Ruocchio è laureato in matematica ed
ha ottenuto la certificazione Oracle come Application Developer. Si occupa di analisi, progettazione e sviluppo di applicazioni software utilizzando Oracle Designer e Developer. Può essere
contattato all’indirizzo [email protected]
58
Db Oracle2_97.PM6
TRASFORMIAMO IN OGGETTO
UNA TABELLA ORACLE
I metodi di ogni tabella presente nel
sistema, saranno contenuti in un package. Nel Listato 2, a titolo d’esempio, è
contenuto lo script di creazione del
package COMUNI_PKG che implementa le principali funzionalità legate alla
tabella dei comuni. Qualunque operazione sulla tabella COMUNI deve essere realizzata mediante il Package
COMUNI_PKG. Nel caso in cui si preveda la necessità di un gran numero
di funzioni legate all’oggetto, si può
progettare la scrittura di due package,
uno per le funzioni generali, l’altro
per quelle più particolari. Si può pensare di definire un package per ogni
entità forte anziché per ogni tabella. Ad
esempio nel caso di un’applicazione che
gestisca ordini d’acquisto, si potrebbe
pensare ad un solo package che gestisce le transazioni sia sulla tabella degli
ordini sia su quella delle righe dell’ordine. La decisione deve essere presa in
fase di progettazione. Il problema fondamentale da affrontare per questo tipo
d’approccio è la documentazione delle
procedure esistenti, al fine di evitare la
crescita incontrollata dei package. E’ fondamentale stabilire un repository delle
informazioni e tenerlo sempre aggiornato. Il repository di Oracle Designer
potrebbe essere lo strumento giusto per
soddisfare questa necessità. A proposito della necessità di utilizzare la stessa
procedura con diverse logiche che dipendono dal contesto, Oracle offre la
possibilità di overloading delle procedure incluse nei package, la quale con-
Figura 1 - Il Data Block Wizard di Oracle Forms
COMPUTER PROGRAMMING N. 97 - DICEMBRE 2000
58
22/11/00, 17.30
ACCESSO DATI
sente di definire n logiche diverse per
lo stesso programma che vengono attivate secondo il tipo e del numero dei
parametri passati. Essa consente una
grande semplificazione del codice, sia
per i programmi che chiamano la procedura, sia per la procedura stessa. Sul
server vanno anche posti i package che
includono le procedure generali che
non sono riconducibili alla singola tabella. Il numero e la struttura di questi oggetti è funzione del progetto. Per
questi package è ancora più sentita l’esigenza di una documentazione capillare per evitare il proliferare incontrollato (e ad un certo punto incontrollabile) delle procedure.
IN ORACLE FORMS
In questo paragrafo si presenta un
rapido excursus delle più comuni problematiche risolte con comandi SQL
nei moduli Oracle Forms.
I blocchi base-table
Un blocco di questo tipo, come noto,
gestisce autonomamente il codice per
inserire, modificare, cancellare e ricercare i dati nella tabella su cui è basato.
In Oracle Developer, a partire dalla
versione 2.0, è possibile basare un blocco non solo su una tabella, ma anche
su delle procedure di database (vedi
Figura 1). Le procedure che dovranno
essere poste alla base del blocco devono essere create nel package-oggetto
(vedi il Listato 2 per un esempio).
Listato 1 - Script di creazione delle tabelle
create table comuni
(codistat
varchar2(7)
nomecomune
varchar2(30)
cap
varchar2(5)
abitanti
number(10));
create table localita
(codloc
number(10)
nomeloc
varchar2(30)
loc_codistat varchar2(7)
not null primary key,
not null,
not null,
not null primary key,
not null,
not null);
alter table localita
add constraint LOC_COM_FK
foreign key (loc_codistat)
references comuni(codistat);
moduli forms, esistono 100 procedure
che controllano l’esistenza del comune
effettuando una select. Se si dovesse
decidere di modificare, in qualche
modo, la tabella COMUNI potrebbe
essere necessario cercare e modificare
100 procedure. Con la tecnica presentata basterebbe modificare solo il package comuni_pkg.
La valorizzazione dei campi di Look-up
In genere, per valorizzare la descrizione del comune (campo non base table nel blocco in cui il codice comune
è FK) si ricorre ad una select sia in
When-Validate-Item sul campo codice
comune che in Post-query sul blocco.
Il codice da utilizzare è il seguente:
message(‘ ‘,no_acknowledge);
raise form_trigger_failure;
end if;
END;
Si noti che è stata riutilizzata la stessa
funzione già impiegata per controllare
l’esistenza del comune quando il codice era foreign-key; per il controllo di
assenza di figli che deve precedere la
cancellazione di un record si possono
applicare due strategie. La prima consiste nello scrivere il seguente codice
in KEY-DELREC:
DECLARE
Errore varchar2(2000) :=
comuni_pkg.check_cancella(:codistat);
BEGIN
if Errore is not null then
Begin
message(Errore);
:dsp_nomecomune := COMUNI_PKG.NOMECOMUNE
Le validazioni di codice
message(‘ ‘,no_acknowledge);
(:LOC_CODISTAT);
Nel trigger When-Validate-Item del
codice di una foreign-key bisogna inserire il codice
BEGIN
if :loc_codistat is not null and not
comuni_pkg.esiste(:loc_codistat) then
message(‘Errore:
Codice Comune non esistente’);
message(‘ ‘,no_acknowledge);
raise form_trigger_failure;
end if;
END;
Il codice precedente (che non tiene
conto della eventuale necessità di valorizzare la descrizione del comune) garantisce migliori prestazioni, e soprattutto una eccezionale mantenibilità, rispetto al codice generato normalmente
da Oracle Designer e dal Data-blockwizard di Oracle Developer. Si tenga
presente che, con la tecnica classica, se
il codice comune è presente in 100
raise form_trigger_failure;
end;
else
L’integrità referenziale
end if;
delete_record;
Ferma restando la volontà di controllare l’integrità referenziale sia a livello
di client sia sul server, e volendo effettuare il controllo in fase di validazione
e non in fase di salvataggio, si possono
applicare le seguenti tecniche: per il
controllo di esistenza delle foreign-key
basta guardare l’esempio proposto nel
caso della validazione; per il controllo
di unicità delle primary ed unique key
si consideri il seguente codice da impostare sia nel When-validate-Item del
campo CODISTAT che nel Pre-insert
del blocco COMUNI (adesso siamo nel
modulo form che gestisce i comuni):
BEGIN
if :codistat is not null and comuni_
pkg.esiste(:codistat) then
message(‘Errore: Codice Comune
già esistente’);
END;
La funzione comuni_pkg.check_ cancella restituisce null quando è possibile
effettuare la cancellazione, altrimenti restituisce un messaggio di errore. Nella
funzione viene effettuato il controllo
su tutti i figli, in essa possono anche
essere implementate particolari regole
di business che limitano in qualunque
modo la possibilità di cancellare un
record, ma bisogna stare attenti a non
dimenticare mai la coesione del codice
(una procedura deve fare tutto e solo
quello che è evidente che faccia). La
seconda strategia perseguibile per il
controllo di assenza di figli prima di
cancellare un record è definire un’unica procedura che, mediante l’utilizzo
di SQL dinamico, gestisca il controllo
per qualunque tabella. La prima delle
due strategie, a fronte di un maggiore
COMPUTER PROGRAMMING N. 97 - DICEMBRE 2000
Db Oracle2_97.PM6
59
22/11/00, 17.30
59
ACCESSO DATI
Listato 2 - Script di creazione del package COMUNI_PKG
create or replace package comuni_pkg is
type tab_comuni is table of comuni%rowtype
index by binary_integer;
function esiste (p_codistat in varchar2)
return boolean;
function nomecomune (p_codistat in varchar2)
return varchar2;
procedure inserisci
(p_tab_comuni in out tab_comuni);
procedure modifica
(p_tab_comuni in out tab_comuni);
procedure cancella
(p_tab_comuni in out tab_comuni);
procedure ricerca
(p_codistat in varchar2,
p_nomecomune in varchar2,
p_cap in varchar2,
p_tab_comuni in out tab_comuni) ;
procedure riserva
(p_tab_comuni in out tab_comuni);
function check_cancella (p_codistat in varchar2)
return varchar2;
end comuni_pkg;
/
create or replace package body comuni_pkg is
function esiste (p_codistat in varchar2)
return boolean is
conta number;
begin
select count(0)
into conta
from comuni
where codistat = p_codistat;
if conta > 0 then
return(true);
else
return(false);
end if;
end;
function nomecomune (p_codistat in varchar2)
return varchar2 is
appo_nome comuni.nomecomune%type;
begin
select nomecomune
into appo_nome
from comuni
where codistat = p_codistat;
return(appo_nome);
exception
when no_data_found then
return(‘Comune Inesistente’);
when too_many_rows then
return(‘Violazione PK’);
end;
procedure inserisci
(p_tab_comuni in out tab_comuni) is
begin
for i in 1..p_tab_comuni.count loop
insert into comuni(codistat, nomecomune,
cap, abitanti)
values (p_tab_comuni(i).codistat,
p_tab_comuni(i).nomecomune,
p_tab_comuni(i).cap,
p_tab_comuni(i).abitanti);
end loop;
end;
procedure modifica
(p_tab_comuni in out tab_comuni) is
begin
60
Db Oracle2_97.PM6
for i in 1..p_tab_comuni.count loop
update comuni set
nomecomune = p_tab_comuni(i).nomecomune,
cap = p_tab_comuni(i).cap,
abitanti = p_tab_comuni(i).abitanti
where codistat = p_tab_comuni(i).codistat;
end loop;
end;
procedure cancella
(p_tab_comuni in out tab_comuni) is
begin
for i in 1..p_tab_comuni.count loop
delete comuni
where codistat = p_tab_comuni(i).codistat;
end loop;
end;
procedure ricerca
(p_codistat in varchar2,
p_nomecomune in varchar2,
p_cap in varchar2,
p_tab_comuni in out tab_comuni) is
cursor c_com is
select *
from comuni
where codistat like nvl(p_codistat,’%’)
and nomecomune like nvl(p_nomecomune,’%’)
and cap like nvl(p_cap,’%’);
i number := 1;
begin
for rec in c_com loop
p_tab_comuni(i).codistat := rec.codistat;
p_tab_comuni(i).nomecomune := rec.nomecomune;
p_tab_comuni(i).cap := rec.cap;
p_tab_comuni(i).abitanti := rec.abitanti;
i := i + 1;
end loop;
if i = 1 then
p_tab_comuni(1).codistat := null;
end if;
end;
procedure riserva
(p_tab_comuni in out tab_comuni) is
appo_cod comuni.codistat%type;
begin
for i in 1..p_tab_comuni.count loop
select codistat
into appo_cod
from comuni
where codistat = p_tab_comuni(i).codistat
for update;
end loop;
end;
function check_cancella (p_codistat in varchar2)
return varchar2 is
conta number;
begin
select count(0)
into conta
from localita
where loc_codistat = p_codistat;
if conta > 0 then
return(‘Esistono delle località ‘||
‘in questo comune!’);
else
return(null);
end if;
end;
end comuni_pkg;
/
COMPUTER PROGRAMMING N. 97 - DICEMBRE 2000
60
22/11/00, 17.31
ACCESSO DATI
dispendio di energie per la codifica,
offre sicuramente maggiore flessibilità,
consentendo di personalizzare il controllo alla singola tabella. Inoltre, anche dal punto di vista delle prestazioni, è preferibile la prima soluzione.
I check constraint
Ultimo tipo di controllo da portare
sul server è quello dei check constraint
di tabella. L’implementazione si realizza creando una procedura nel package
per effettuare il controllo di integrità e
richiamandola in pre-insert e pre-update, con codice del tutto analogo a quello
visto in precedenza per il key-delrec.
Gli insiemi di record
Nel caso in cui fosse necessario ottenere nel modulo form un insieme di
record (ad esempio per popolare un
blocco non base table) si potrebbe seguire la seguente procedura:
1. Definire nel package una PL/SQL
table.
2. Definire nel package una procedura che popola la PL/SQL table con
i dati opportuni (e che magari restituisca il numero di righe create).
3. Definire nel package una procedura che restituisce l’i-esima riga della PL/SQL table.
Il modulo form dovrà solo invocare
una volta la procedura che popola la
table ed n volte, in un loop, la procedura che restituisce la riga i-esima per
popolare l’i-esima riga del blocco.
A partire dalla versione 2.0 di Developer è possibile definire anche in
un modulo form tipi di dato strutturati, dunque è possibile utilizzare
le PL/SQL table. L’utilizzo massivo
di PL/SQL table potrebbe causare
problemi di memoria. Bisogna ricordare sempre che la table resta in memoria per tutta la sessione e quindi
è il caso di scaricarla quando non
serve più. Per scaricare un PL/SQL
table è possibile utilizzare il comando
‘<nome-table>.delete’ oppure definire
una table del tutto identica a quella
che si intende scaricare ma contenente
tutti valori NULL, e assegnare alla table da scaricare la table NULL con il
comando <nome-table>:= <nome table-null>. Qualunque sia la tecnica
scelta per scaricare la table, è necessario inserire, dopo la cancellazione,
l’istruzione dbms_session. free_ unused_ user_memory per rilasciare la memoria allocata per la table. I proble-
Figura 2 - La voce di menù che consente di generare un Table API dal Design
editor di Oracle Designer
mi di memoria sono reali solo quando la table è di notevoli dimensioni.
L’attribuzione automatica di codici
Spesso è necessario assegnare automaticamente il codice identificativo ad
un record. Questa funzionalità viene
realizzata utilizzando sequence, code
control table oppure altre logiche legate alla specifica situazione (ad es. select
max(ID) + 1 from tabella). La logica
di assegnazione dell’ID potrebbe variare nel corso del progetto. È consigliabile realizzare questa funzionalità creando una funzione, nel package legato
alla tabella, che restituisce l’ID ed utilizzare tale funzione nel modulo form.
Ad esempio si voglia assegnare automaticamente l’ID al cliente. Dopo avere definito la funzione ASSEGNA_ID
nel package CLIENTE_PKG, in codice da utilizzare in Pre-Insert (oppure
in When-Create-Record) è il seguente:
BEGIN
:CLI_ID := CLIENTE_PKG.ASSEGNA_ID;
END;
Questo codice prescinde dalla tecnica di assegnazione dell’ID.
LE LISTE DI VALORI
Come detto in precedenza, non è consigliabile spostare sul server i comandi
SQL utilizzati per le liste di valori. Ad
ogni modo è possibile, e fortemente
consigliato, ridurre al minimo il numero dei record group utilizzati nell’applicazione. Infatti, mentre le LOV
sono legate alla particolare funzione in
cui vengono utilizzate dal column mapping, i record group possono essere
riutilizzati più volte nell’applicazione
senza alcuna modifica. Conviene, dunque, creare un modulo form (oppure
una Object Library a partire dalla versione 2.0 di Developer) da usare come
contenitore dei record group ed utilizzare referenze al contenitore in tutte
le form dell’applicazione. Questa tecnica garantisce molteplici vantaggi:
Miglioramento delle prestazioni,
poiché utilizzando più volte le stesse istruzioni SQL si aumentano gli
accessi alla SGA e si diminuiscono
le richieste di parsing dell’istruzione al server.
Miglioramento della mantenibilità,
perché il codice è centralizzato.
Miglioramento dell’interfaccia utente, perché, in qualunque punto del
sistema, si vedranno sempre gli
stessi dati richiedendo una specifica lista.
Riduzione degli errori di codifica,
perché bisogna scrivere meno volte
il codice per ottenere le liste.
IN ORACLE DESIGNER
Utilizzando Oracle Designer è possibile generare automaticamente i comandi DDL per la creazione degli oggetti
COMPUTER PROGRAMMING N. 97 - DICEMBRE 2000
Db Oracle2_97.PM6
61
22/11/00, 17.31
61
ACCESSO DATI
dice opportuno (vedi il punto sulle
validazioni).
Per applicare queste regole aumentano leggermente i costi di disegno del
modulo. Questo vale soprattutto quando sono presenti molti campi di lookup in mappa. Il maggior costo di disegno è ampiamente bilanciato dal beneficio di avere un programma più mantenibile. Per la gestione delle LOV come
indicato nel paragrafo precedente, è
necessario cancellare i record group
generati da Oracle Designer ed applicare alle LOV generate i record group
referenziati.
CONCLUSIONI
Figura 3 - La selezione delle tabelle per cui si intende generare il Table API
di database. A partire dalla versione
2.0 è possibile anche generare il
package dei metodi di una tabella,
detto table API (vedi Figure 2 e 3).
Il package generato è molto più completo e complesso del nostro
COMUNI_PKG. Oltre alle procedure di insert, update, delete, lock e
select, sono presenti procedure per
la validazione di check constraint,
per l’update e la delete in cascata e
per la gestione dei campi calcolati
(che nel nostro caso non sono presenti). Il problema che risulta evidente guardando il codice generato
dal CASE è che le procedure di manipolazione dei dati gestiscono solo
un record per volta anziché tabelle
di record. Per essere utilizzato in
Oracle Developer, come illustrato nel
paragrafo precedente, è necessario modificare il package generato in modo
da consentire la gestione di tabelle di
record. Veniamo al problema della generazione automatica del codice Developer. Fare in modo che il CASE non
generi il controllo dei constraint è semplice, basta impostare a SERVER la
proprietà VALIDATE IN delle primary, foreign e unique key nonché dei
check constraint. Allo scopo di generare correttamente il codice di valorizzazione dei campi di look-up bisogna
seguire la seguente procedura nel dia-
62
Db Oracle2_97.PM6
gramma del modulo: (immaginiamo di
avere il campo LOC_CODISTAT sulla tabella LOCALITA e di voler vedere, per ogni località, la descrizione del
comune in cui si trova la località)
1. Creare un Secondary column usage sulla tabella LOCALITA.
2. Nella derivation expression del secondary column usage inserire la
seguente stringa:
COMPLEX: COMUNI_PKG.NOMECOMUNE
(LOC_CODISTAT)
3. Avere cura, in fase di generazione
del modulo, di valorizzare correttamente la connect string della tab
‘Compile’ in modo che il generatore ‘veda’ gli oggetti sul giusto DB.
4. Nel caso in cui sul codice ci fosse
anche una LOV, avere cura di rimuovere lo usage DISPLAY per la
descrizione. In ogni caso viene creato il campo per la visualizzazione
della descrizione dalla LOV, esso è
DISPLAYED=FALSE e può essere
cancellato dopo avere modificato opportunamente il column mapping.
5. In ogni caso il generatore crea il
codice di validazione della Foreign
key quando è presente una LOV.
In tal caso bisogna sostituire il codice generato dal CASE con il co-
All’inizio di questo articolo si è parlato di Object-Oriented. La filosofia OO
si ispira ad una regola fondamentale:
ogni oggetto sa quali attività può svolgere con i propri dati e come svolgerle. Non è logico disseminare il sistema
di select che rispondono alla banale
domanda: esiste un comune con il codice ‘63048’? a questa domanda può e
deve rispondere l’oggetto Comune. In
termini di prestazione i vantaggi sono
evidenti. La prima volta che è richiamato, un package viene caricato interamente in SGA e reso disponibile a
tutti gli utenti con tempi di risposta
più brevi. Inoltre il carico di lavoro
sulla rete è nettamente inferiore con
questa tecnica di sviluppo. Il vantaggio
più grande di questa filosofia è sicuramente l’eccezionale mantenibilità del
codice. Qualunque cambiamento della
logica che soggiace alla realizzazione
di un’attività è assolutamente ininfluente rispetto al codice che si esegue sul
client. Un esempio evidente è quello
dell’integrità referenziale. All’aggiunta
di nuove foreign keys legate ad una
data tabella bisognava ricercare in tutti
i moduli forms le possibili manchevolezze nei controlli di integrità referenziale sul client. Con la nuova tecnica,
invece, basta modificare la procedura
(unica) che controlla se ci sono figli
prima di consentire la cancellazione.
COMPUTER PROGRAMMING N. 97 - DICEMBRE 2000
62
BIBLIOGRAFIA
1. J.C. Lennon, “Towards object orientation using stored procedures and functions”, ODTUG, 1997.
2. S. Fuerstain, “Advanced Oracle PL/
SQL, programming with packages”,
O'Reilly and Associates, 1995.
3. B. Wright, “Object Orientation in Forms 4.5”, Oracle white paper, 1994.
4. P. Dorsey e P. Koletzke, “VRAD:
Very Rapid Application Development in
Forms 5.0”, ODTUG, 1997.
22/11/00, 17.31