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 unapplicazione 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. Questarticolo è ispirato alla seguente regola: nessunistruzione 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). Lapplicazione di questa semplice regola, come vedremo, consente di migliorare notevolmente sia le prestazioni sia la mantenibilità del codice. In questarticolo 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 allindirizzo [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 desempio, è 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 alloggetto, si può progettare la scrittura di due package, uno per le funzioni generali, laltro per quelle più particolari. Si può pensare di definire un package per ogni entità forte anziché per ogni tabella. Ad esempio nel caso di unapplicazione che gestisca ordini dacquisto, si potrebbe pensare ad un solo package che gestisce le transazioni sia sulla tabella degli ordini sia su quella delle righe dellordine. La decisione deve essere presa in fase di progettazione. Il problema fondamentale da affrontare per questo tipo dapproccio è 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 lesigenza 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 lesistenza 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 lesistenza 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 Lintegrità referenziale end if; delete_record; Ferma restando la volontà di controllare lintegrità 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 lesempio 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 ununica procedura che, mediante lutilizzo 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. Limplementazione 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 li-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 li-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. Lutilizzo 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, listruzione 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. Lattribuzione 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 dellID potrebbe variare nel corso del progetto. È consigliabile realizzare questa funzionalità creando una funzione, nel package legato alla tabella, che restituisce lID ed utilizzare tale funzione nel modulo form. Ad esempio si voglia assegnare automaticamente lID 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 dellID. 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 nellapplicazione. Infatti, mentre le LOV sono legate alla particolare funzione in cui vengono utilizzate dal column mapping, i record group possono essere riutilizzati più volte nellapplicazione 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 dellapplicazione. 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 dellistruzione al server. Miglioramento della mantenibilità, perché il codice è centralizzato. Miglioramento dellinterfaccia 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 lupdate 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- Allinizio 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 loggetto 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 leccezionale mantenibilità del codice. Qualunque cambiamento della logica che soggiace alla realizzazione di unattività è assolutamente ininfluente rispetto al codice che si esegue sul client. Un esempio evidente è quello dellintegrità referenziale. Allaggiunta 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