In molte applicazioni di tipo enterprise, la logica di business viene talvolta incapsulata all'interno di particolari programmi localizzati all'interno del database: le Stored Procedure.
Le Stored Procedure sono paragonabili ai metodi del codice Java. Infatti, sono dotate di un nome, di una serie di parametri e di un body contenente una serie di istruzioni di tipo SQL. Al termine dell'esecuzione, le stored procedure possono restituire dei valori.
I vantaggi nell'uso delle Stored Procedure
Ci sono svariati vantaggi nell'utilizzare le Stored Procedure per le proprie applicazioni:
- Attraverso una Stored Procedure è possibile raggruppare un insieme di comandi SQL che forniscono, nel loro insieme, una determinata funzionalità. Ogni client può accedere a tale funzionalità senza bisogno di effettuare più chiamate di tipo JDBC in cascata.
- Si possono riutilizzare le Stored Procedure già sviluppate piuttosto che riscrivere da zero il codice che esegue le medesime funzionalità
- Le Stored Procedure rendono più agevole il controllo della transazionalità delle operazioni su DB
- L'incapsulamento di una determinata funzionalità all'interno di una stored procedure garantisce che ogni parte dell'applicazione che richieda tale funzionalità, riceva un risultato basato sempre sul medesimo algoritmo. Inoltre, se l'algoritmo dovesse cambiare, sarà sufficiente apportare le modifiche soltanto alla stored procedure e usufruire dei cambiamenti apportati senza riscrivere il codice dell'applicazione.
Tuttavia, è bene sapere che, nonostante i vantaggi sopra elencati, non tutti i database prevedono l'utilizzo delle stored procedure.
Creazione di un CallableStatement
Il codice JDBC è in grado di invocare le stored procedure utilizzando un oggetto di tipo CallableStatement
. La creazione di tale oggetto è molto simile a quella che abbiamo visto nell'articolo sui Prepared Statement e si basa su uno dei tre metodi seguenti, messi a disposizione dalla classe Connection:
prepareCall (String sql)
: Crea un oggetto di tipo CallableStatement relativo alla stringa SQL passata in input. Qualora venga restituito un resultset associato al Prepared Statement, questo sarà di tipo forward-only e non holdable.prepareCall(String sql, int resultSetType, int resultSetConcurrency)
: Come il precedente metodo con la differenza che il tipo di resultset e la concorrenza dello stesso resultset sono impostati in input. Anche in questo caso, il recordset sarà non holdable.prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
: Come il precedente, ma con la capacità di assegnare anche l'holdability attraverso il parametro di input.
Il primo argomento passato a tutti e tre i metodi prepareCall
rappresenta una stringa SQL utilizzata per invocare una stored procedure. In generale, questa stringa può assumere svariate forme ma esistono delle regole che sono valide per tutte le tipologie di tali stringhe. In particolare, ci riferiamo all'utilizzo della stringa "call
" che deve comparire appena prima del nome della stored procedure e alle parentesi graffe che hanno il compito di racchiudere il costrutto SQL. Questi accorgimenti servono per indicare al driver del DB che la stringa passata non è riconducibile a una istruzione SQL ordinaria ma che si tratta, invece, di una stringa che deve essere convertita in una forma adatta per l'invocazione di una stored procedure.
La forma più semplice della stringa in input è quella che fa riferimento a una stored procedure che non richiede alcun parametro:
{ call nome_stored_procedure }
Per esempio, se supponiamo che il nostro database contenga una stored procedure denominata deleteEmployees
, che non prenda alcun parametro in input e non restituisca alcun valore, il codice java che crea un oggetto di tipo CallableStatement avrà la seguente forma:
String sql = "{ call deleteEmployees }";
CallableStatement cs = connection.prepareCall(sql);
Nel caso in cui una stored procedure richieda dei parametri in input ma non restituisca nessun valore di ritorno, il codice diventa:
String sql = "{ call setSalary (?, ?) }";
CallableStatement cs = connection.prepareCall(sql);
dove, con l'utilizzo dei placeholder, si è marcato ogni singolo parametro richiesto dalla stored procedure.
Infine, laddove una stored procedure (con parametri in input) restituisse dei valori, il codice corretto per la sua invocazione sarà:
String sql = "{ ? = call get_Employee (?) }";
CallableStatement cs = connection.prepareCall(sql);
Anche in questo caso, come si può notare, si è utilizzato un placeholder per il valore restituito dalla stored procedure.
Come utilizzare i CallableStatement
Allo stesso modo di come avviene nei PreparedStatement, anche nei CallableStatement i placeholder prevedono una numerazione sequenziale, partendo dal numero 1 associato al placeholder più a sinistra nella stringa e incrementando tale valore man mano che si scorre verso destra.
Se un placeholder è utilizzato per passare un argomento di input a una stored procedure (tali parametri sono anche detti IN parameters) il suo valore deve essere impostato prima che l'istruzione possa essere eseguita. Nel caso in cui tale impostazione non venisse effettuata, il driver scatenerà una eccezione di tipo SQLException nel momento in cui si tentasse di eseguire il costrutto SQL.
L'impostazione dei parametri di input avviene nello stesso modo utilizzato per i Prepared Statement, ovvero attraverso l'uso dei metodi:
setTIPO()
dove TIPO rappresenta un tipo di dato in Java. Ciò è possibile in quanto l'interfaccia CallableStatement deriva dalla PreparedStatement.
Nelle stored procedure, però, è possibile utilizzare anche parametri di Output (definiti OUT parameters) o parametri di Input/Output (definiti INOUT parameters) che consentono di impostare un valore in input e riceverne un altro in output. In questi casi, è necessario che ogni OUT e INOUT parameter venga registrato prima che l'invocazione alla stored procedure possa essere eseguita. La registrazione avviene attraverso l'utilizzo dei seguenti metodi:
void registerOutParameter (int parameterIndex, int jdbcType)
void registerOutParameter (int parameterIndex, int jdbcType, int scale)
Il parametro parameterIndex
rappresenta la posizione del placeholder all'interno della stringa SQL. Il parametro jdbcType
è invece valorizzato da una delle costanti definite nella classe java.sql.Types
in cui vengono mappati i tipi di dati utilizzati all'interno dei costrutti SQL.
Se volessimo invocare una stored procedure che riceve come parametro da restituire in output una stringa, posizionata come secondo parametro della richiesta, sarà necessario invocare il metodo:
registerOutParameter (2, java.sql.types.STRING);
Oppure, nel caso venisse restituito un double associato al parametro in posizione 3
registerOutParameter (3, java.sql.types.DOUBLE);
Come si può notare, però, esiste una seconda definizione del metodo registerOutParameter
che richiede un terzo parametro (scale). È possibile utilizzare questo metodo quando si deve registrare un parametro che appartiene a uno dei tipi di dati numerici come double, float, numeric o decimal e definire attraverso il parametro scale il numero di decimali posti dopo la virgola. Per esempio:
registerOutParameter (3, java.sql.types.DOUBLE, 2);
restituirà un double in output con 2 decimali dopo la virgola.
Anche per i Callable Statement vale il vantaggio del riutilizzo: infatti, una volta che un placeholder è stato impostato con un certo valore, il suo contenuto non cambia a meno di una esplicita richiesta effettuata via codice.
Dopo l'impostazione dei placeholder, non rimane che invocare il metodo che comunichi al database di eseguire la stored procedure.
Il metodo va ricercato in uno dei seguenti tre: executeUpdate()
, executeQuery()
o execute()
.
Si noti l'assenza di parametri, condizione necessaria per i Callable Statement affinché il driver JDBC non scateni una SQLException.