martedì 24 marzo 2015

Programmazione PL-SQL

Vai all'indice

Nel precedente articolo ho introdotto PL/SQL il linguaggio di programmazione proprietario di Oracle. Eseguire operazioni pesanti direttamente nel database tramite PL/SQL rende l'elaborazione dell'applicazione più leggera e stabile, tuttavia, nel caso le operazioni da eseguire nel DB non siano particolarmente onerose, conviene mantenere le query all'interno dell'applicazione, per facilità di manutenzione e per la portabilità verso altri datasource. Nel precedente articolo vi ho mostrato il funzionamento di una semplice stored procedure, tuttavia esistono altri costrutti utili ed interessanti.

Le function

Le function sono dei blocchi di codice che restituiscono un valore in output, di seguito un semplice esempio:

-- Nota: mai creare i propri oggetti nel SYS schema.
  
CREATE OR REPLACE FUNCTION UtentiTotali
RETURN number IS
   totali number(2) := 0;
BEGIN
   SELECT count(*) into totali
   FROM Utente;
   
   RETURN totali;
END;
/


La funzione UtentiTotali esegue un conteggio dei record all'interno della tabella Utente e lo memorizza nella variabile numerica totali, la quale viene restituita come risultato della funzione. Eseguo la funzione in SQLPLUS.
Definisco una variabile itot di tipo numerico,
SQL>var itot number;

inserisco il codice all'interno di un file e genero la function
SQL>@D:path/file.sql;

Eseguo la function assegnando il risultato alla variabile itot appena definita.
SQL>exec :itot := utentiTotali; 

Infine stampo a video il risultato.
SQL>print itot; 

I trigger

I trigger sono programmi che vengono eseguiti al verificarsi di un evento


  
CREATE OR REPLACE TRIGGER display_ruolo_changes
BEFORE DELETE OR INSERT OR UPDATE ON myuser.myUtente
FOR EACH ROW
WHEN (NEW.ID > 0)
DECLARE
BEGIN
   dbms_output.put_line('Old ruolo: ' || :OLD.ruolo);
   dbms_output.put_line('New ruolo: ' || :NEW.ruolo);
   dbms_output.put_line('il ruolo è stato modificato');
END;
/


Il Trigger esegue il blocco di codice definito all'interno delle istruzioni begin..end prima dell'esecuzione di operazioni di insert, delete o update nella tabella Utente. La clausula "for each row" indica che viene eseguito una volta per ogni riga interessata dall'evento. I riferimenti OLD e NEW indicano il valore del campo prima e dopo l'esecuzione dell'operazione di DML. Per testare il trigger eseguo l'istruzione:
SQL>@D:path/trigger.sql

L'operazione genera un messaggio di errore.
ERROR at line 1:
ORA-04089: cannot create triggers on objects owned by SYSERROR at line 1:

Nello specifico Oracle impedisce di generare i trigger tra gli oggetti di sistema. In realtà è buona norma definire un proprio schema in cui generare tutti i propri oggetti.

In quest'ottica genero un tablespace personale:
SQL> create tablespace mytabspace
2 datafile 'mytabspace.dat'
3 size 10m autoextend on;


Creo un'utenza personale a cui associo il tablespace appena creato.
SQL> create user myuser
2 identified by passwd
3 default tablespace mytabspace;


Associo all'utenza i permessi per la creazione di sessioni, tabelle, e trigger
SQL> grant create session to myuser;
SQL> grant create table to myuser;
SQL> grant create trigger to myuser;
SQL> grant unlimited tablespace to myuser;


Accedo all'utenza appena creata e genero gli oggetti di cui ho bisogno compreso il trigger che voglio testare.
SQL> conn myuser;
SQL> CREATE TABLE myUtente("ID" NUMBER(*,0) NOT NULL ENABLE, "NOME" VARCHAR2(30), "COGNOME" VARCHAR2(30), "RUOLO" VARCHAR2(30), PRIMARY KEY ("ID"));

SQL> insert into myUtente (ID, NOME, COGNOME, RUOLO) VALUES (1, 'FRANCO', 'NERI', 'CAPOUFFICIO');
SQL> insert into myUtente (ID, NOME, COGNOME, RUOLO) VALUES (2, 'MARIO', 'BIANCHI', 'IMPIEGATO');
SQL> insert into myUtente (ID, NOME, COGNOME, RUOLO) VALUES (3, 'MARCO', 'ROSSI', 'APPRENDISTA');


Rigenero il trigger dopo avere corretto il riferimento dalla tabella Utente a myUtente
SQL>@D:path/trigger.sql

Eseguo una Update sul ruolo di un singolo record ed ottengo il risultato atteso.
SQL> set serveroutput on;
SQL> update myUtente set ruolo = 'Impieganto' where id = 3;

Old ruolo: Apprendista
New ruolo: Impieganto

il ruolo è stato modificato.


I pacchage

I package raggruppano oggetti PL/SQL logicamente correlati come variabili e sottoprogrammi.
Un pacchetto ha due parti obbligatorie:
  • Package Specification
  • Package Body
Il Package Specification è la l'interfaccia del package. Essa dichiara i tipi, variabili, costanti, eccezioni, cursori e sottoprogrammi che possono essere referenziate al di fuori del pacchetto. Il altre parole contiene tutte le informazioni sul contenuto del pacchetto, ma esclude il codice per i sottoprogrammi.
Il Package Body contiene il codice dei vari metodi dichiarati nello specifico pacchetto e altre dichiarazioni private che non vengono mostrate all'esterno al pacchetto.

Riporto di seguito un esempio pratico.
Genero il Package Specification.
  
CREATE OR REPLACE PACKAGE c_package AS
   -- Aggiunta nuovo utente
   PROCEDURE addUtente(c_id   myutente.id%type,
   c_nome  myutente.nome%type,
   c_cognome  myutente.cognome%type,
   c_ruolo myutente.ruolo%type);
  
   -- Elimina utente
   PROCEDURE delUtente(c_id  myutente.id%TYPE);
   --Lista utenti
   PROCEDURE listaUtenti;

END c_package;
/


Genero il Package Body.
  
CREATE OR REPLACE PACKAGE BODY c_package AS
   PROCEDURE addUtente(c_id  myUtente.id%type,
      c_nome myUtente.nome%type,
      c_cognome  myUtente.cognome%type,
      c_ruolo  myUtente.ruolo%type)
   IS
   BEGIN
      INSERT INTO myUtente (id,nome,cognome,ruolo)
         VALUES(c_id, c_nome, c_cognome, c_ruolo);
   END addUtente;
  
   PROCEDURE delUtente(c_id   myUtente.id%type) IS
   BEGIN
       DELETE FROM myUtente
         WHERE id = c_id;
   END delUtente;

   PROCEDURE listaUtenti IS
   CURSOR c_utente is
      SELECT  nome FROM myUtente;
   TYPE c_list is TABLE OF myUtente.nome%type;
   name_list c_list := c_list();
   counter integer :=0;
   BEGIN
      FOR n IN c_utente LOOP
      counter := counter +1;
      name_list.extend;
      name_list(counter)  := n.nome;
      dbms_output.put_line('Utente(' ||counter|| ')'||name_list(counter));
      END LOOP;
   END listaUtenti;
END c_package;
/


Il blocco di codice che riporto di seguito utilizza i metodi dichiarati e definiti nel pacchetto c_package.
  
DECLARE
   code myUtente.id%type:= 8;
BEGIN
      c_package.addUtente(7, 'Maria', 'Viola', 'Impiegata');
      c_package.addUtente(8, 'Gianni', 'Verdi', 'Apprendita');
      c_package.listaUtenti;
      c_package.delUtente(code);
      c_package.listaUtenti;
END;
/


Per testare i programmi appena creati su SQLPLUS è sufficiente inserirli su dei file .sql e mandarli in esecuzione con le stesse modalità già viste in precedenza.

Hai apprezzato questo post? Conferma le mie competenze o scrivi una segnalazione sul mio profilo Linkedin!

giovedì 19 marzo 2015

Oracle e PL-SQL

Vai all'indice

Finora negli esempi proposti ho utilizzato per comodità JavaDB (una versione di Apache Derby) un database leggero presente all’interno della JDK. Tuttavia sviluppando applicazioni "Enterprise" è opportuno utilizzare una base di dati più performante e completa. In quest'ottica Oracle può essere una delle scelte migliori. Dal sito della Oracle è possibile scaricare la versione 11g Express, l'edizione gratuita e più leggera del prodotto. Una volta eseguita l'istallazione è possibile dialogare con Oracle tramite la console operativa SQLPLUS semplicemente accedendo al prompt e digitando il comando:
sqlplus "/ as sysdba".
Uno degli aspetti più interessanti di Oracle è la possibilità di scrivere veri e propri programmi utilizzando PL/SQL un linguaggio di programmazione procedurale che mette a disposizione, oltre a tutti i costrutti dell'SQL, anche i controlli condizionali, i cicli iterativi, la gestione delle variabili e delle costanti, la gestione delle eccezioni, delle procedure, delle funzioni, ecc.
Un concetto fondamentale del PL/SQL è quello di blocco al cui interno è possibile combinare logicamente i vari comandi SQL nonché i vari costrutti "accessori" messi a disposizione dal PL/SQL. Di seguito riporto un esempio pratico di programma PL/SQL.

       
CREATE OR REPLACE PROCEDURE cursore
IS
BEGIN
DECLARE
   c_id utente.id%type;
   c_nome utente.nome%type;
   c_cognome utente.cognome%type;
   CURSOR c_utente is
      SELECT id, nome, cognome FROM utente;
BEGIN
   OPEN c_utente;
   LOOP
      FETCH c_utente into c_id, c_nome, c_cognome;
      EXIT WHEN c_utente%notfound;
      dbms_output.put_line(c_id || ' ' || c_nome || ' ' || c_cognome);
   END LOOP;
   CLOSE c_utente;
EXCEPTION
   WHEN no_data_found THEN
      dbms_output.put_line('Nessun utente trovato!');
   WHEN others THEN
      dbms_output.put_line('Errore generico!');
END;
END;
/


Il blocco di codice definisce un cursore ovvero un costrutto che permette di gestire le tuple restituite dall'esecuzione di una query. Il risultato finale è la stampa a video dei campi Id, Nome e Cognome della tabella Utente.
Nella sezione DECLARE definisco il cursore e le variabili con lo stesso tipo del campo definito in tabella Utente. Nel corpo della procedura apro il cursore e definisco un ciclo che estrae i campi direttamente nelle variabili definite in precedenza nella declare, quindi terminato il ciclo chiudo il cursore. Nella sezione Exception inserisco 2 codici predefiniti che intercettano e stampano a video un messaggio in caso di errori generici o di utente non trovato.
Per eseguire il programma appena descritto riapro SQLPLUS e creo la tabella Utente con la stessa struttura già vista diverse volte in questo blog.

  
-- Nota: mai creare i propri oggetti nel SYS schema.
SQL> CREATE TABLE Utente("ID" NUMBER(*,0) NOT NULL ENABLE, "NOME" VARCHAR2(30), "COGNOME" VARCHAR2(30), "RUOLO" VARCHAR2(30), PRIMARY KEY ("ID"));


Inserisco il codice del programma PL/SQL in un file di testo che chiamo cursore.sql. Col seguente comando eseguo la creazione della Stored Procedure.
SQL> @C:/pathdelfile/cursore.sql;
Se la creazione è andata a buon fine il processo restituisce il messaggio "Procedure created." , quindi eseguo il programma:
SQL> exec cursore();
Se il processo termina correttamente vedremo a video la lista degli utenti.
Hai apprezzato questo post? Conferma le mie competenze o scrivi una segnalazione sul mio profilo Linkedin!

martedì 10 marzo 2015

Gestire la transazionalità con Spring

Vai all'indice

In questo post approfondirò un aspetto di cui finora ho solo accennato, ma che ha una grande importanza nelle applicazioni J2EE, ovvero la gestione della transazionalità con Spring.
Una transazione è una sequenza di azioni svolte in un database che vengono trattate come una singola unità di lavoro . Queste azioni dovrebbero o essere interamente completate o non prevedere alcun effetto nel caso qualcosa vada storto. La gestione delle transazioni è una parte importante di un RDBMS orientato alle applicazioni enterprise per assicurare l’integrità e la coerenza dei dati . Il concetto di transazionalità può essere definito dalle seguenti quattro proprietà descritte dall’acronimo inglese ACID:


  • Atomicity: Una transazione deve essere trattata come una singola unità di lavoro quindi se una sola operazione dell'intera sequenza di operazioni della transazione non avrà esito positivo, allora tutta la transazione non andrà a buon fine. 
  • Consistency: Rappresenta la consistenza dell'integrità referenziale del database, chiavi primarie univoche nelle tabelle.
  • Isolation: Ci possono essere molte transazioni che elaborano gli stessi dati nello stesso momento, ogni transazione dove essere isolata dalle altre per prevenire la corruzione dei dati.
  • Durability: Una volta che una transazione è stata completata, i risultati di tale operazione devono essere resi permanenti e non possono essere cancellati dal database a causa di una anomalia.

Un database RDBMS deve garantire tutte le quattro proprietà per ogni transazione . Da un punto di vista pratico al termine di una transazione dovrà essere eseguita la commit per consolidare le operazioni, in caso contrario sarà eseguito il rollback su tutte le operazioni.
Il framework Spring fornisce un livello astratto al di sopra delle sottostanti API per la gestione delle transazioni.
 

Transazioni locali e transazioni globali


Le transazioni locali sono specifiche di una singola risorsa come ad esempio una connessione JDBC, mentre le operazioni globali possono occupare più risorse transazionali come ad esempio una transazione in un sistema distribuito .
La gestione delle transazioni locali può essere utile in un ambiente informatico centralizzato in cui le risorse delle applicazioni si trovano in un unico sito, e la gestione delle transazioni comporta solo un gestore di dati locali in esecuzione su una singola macchina.
La gestione globale delle transazioni è necessaria dove risorse sono distribuite su più sistemi. In tal caso, la gestione delle transazioni deve essere fatto sia a livello locale e globale. Pertanto una transazione globale distribuita viene eseguita su più sistemi, e la sua esecuzione richiede un coordinamento tra il sistema di gestione delle transazioni globali e tutti i gestori locali di dati di tutti i sistemi coinvolti.

Transaction management programmatica o dichiarativa.


Spring supporta due tipi di transaction management.
  1. Transaction management programmatica: significa gestire la transazione tramite la programmazione. Questo dà una estrema flessibilità, ma è difficile da mantenere.
  2. Transaction management dichiarativa: è l’approccio preferibile in quanto separa il transaction management dal codice. Questa metodologia è più semplice da gestire poiché con qualche configurazione XML e l’uso delle annotazioni si applica una funzionalità comune in tutto il codice.
Per utilizzare la gestione delle transazioni tramite le annotazioni è sufficiente aggiungere 3 bean nel file di configurazione xml :

  • <context:annotation-config/>: indica al framework Spring di collegare le annotazioni con le classi e con i metodi.
  • <tx:annotation-driven/>: Aggiunge automaticamente il supporto alle transazioni al fine includere il codice in ambito transazionale.
  • Inizializzare il bean DataSourceTransactionManager con i parametri di connessione.

Di seguito sono riportati i tag xml da inserire nel file di configurazione di Spring per abilitare l’annotazione @Transactional. 

<context:annotation-config/>
<!-- Questo tag abilita le annotations transactions -->
<tx:annotation-driven  transaction-manager="transactionManager"/>
<bean id="transactionManager"
  class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName" value="org.apache.derby.jdbc.ClientDriver"></property>
  <property name="url" value="jdbc:derby://localhost:1527/derby/derbyDB"></property>
  <property name="username" value="test"></property>
  <property name="password" value="test"></property>
</bean>


Uso dell’annotazione @Transactional
L’annotazione @Transactional a livello di classe include tutti i metodi di quella classe nell’ambito transazionale. L’annotazione @Transactional possiede proprietà come: readOnly, isolation, propagation, rollbackFor, noRollbackFor, utilizzate per controllare la transazione o comunicare con altre transazioni in corso. La proprietà readonly=true è utilizzata per indicare che un metodo verrà utilizzato esclusivamente per eseguire query di selezione, mentre impostando readonly=false potremo eseguire anche operazioni come: update, insert e delete. Di default readonly è impostato a false. Altra proprietà interessante è rollbackFor e noRollbackFor.Di default Spring esegue il RollBack di una transazione per le eccezioni di tipo RuntimeException o di eccezioni non controllate.
Altre proprietà interessanti sono Isolation, che indica la misura in cui una transazione è isolata dal lavoro di altre transazioni, e Propagation che specifica il comportamento nel caso in cui un metodo transazionale viene eseguito quando esiste già un contesto di transazione.


Conclusioni
La transazionalità è un tema molto importante e grazie a Spring è possibile gestirla con un alto livello di astrazione. Questo post dà una panoramica generale su questo tema, tuttavia gli aspetti da approfondire sono molteplici. La documentazione dettagliata è disponibile qui.

Hai apprezzato questo post? Conferma le mie competenze o scrivi una segnalazione sul mio profilo Linkedin!